Skip to content

# 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`

    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

Що робить: 1. Відправляє POST на `/internal/nodes/register-or-update` 2. При успіху — відправляє початковий heartbeat 3. При помилці — 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

Що перевіряє: 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()

## Використання

### При першому деплої ноди

    # 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 оновлює статус | ✅

## Наступні кроки

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