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:
Apple
2025-11-30 13:52:01 -08:00
parent 0c7836af5a
commit bca81dc719
36 changed files with 10630 additions and 55 deletions

View 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** — з'єднання нод між собою