# Застосування production-patch AgroMatrix/Stepan на НОДА1

Патч: `agromatrix_stepan_noda1_prod.patch` — тільки зміни для стабілізації Степана/AgroMatrix. Без vision/router/інших агентів.

---

## Передумови

- Доступ по SSH на НОДА1 (144.76.224.179).
- Репозиторій на сервері: `/opt/microdao-daarion` (або ваш `DEPLOY_ROOT`).
- На сервері є каталоги: `gateway-bot/`, `crews/`, `packages/agromatrix-tools/`.

---

## Крок 1 — Завантаження patch на НОДА1

**Варіант A (з локальної машини):**
```bash
scp /шлях/до/agromatrix_stepan_noda1_prod.patch USER@144.76.224.179:/opt/microdao-daarion/
```

**Варіант B (якщо patch уже в репо):**
```bash
ssh USER@144.76.224.179 "cd /opt/microdao-daarion && git pull && git show HEAD:agromatrix_stepan_noda1_prod.patch > agromatrix_stepan_noda1_prod.patch"
# або просто скопіювати вміст файла в репо на сервер
```

**Варіант C (вміст патчу вставлений вручну):**
На сервері створити файл `/opt/microdao-daarion/agromatrix_stepan_noda1_prod.patch` з повним вмістом unified diff.

---

## Крок 2 — Застосування patch (git apply)

На НОДА1:

```bash
cd /opt/microdao-daarion

# Переконатися, що немає незакомічених змін у задіяних файлах (або зробити backup)
git status

# Сухий прогон (перевірка, що патч застосується без конфліктів)
git apply --check agromatrix_stepan_noda1_prod.patch

# Застосувати
git apply agromatrix_stepan_noda1_prod.patch
```

Якщо `git apply --check` повертає помилку (наприклад, через відмінний базовий коміт), можна спробувати:
```bash
git apply --reject --whitespace=fix agromatrix_stepan_noda1_prod.patch
# і вручну розв’язати файли *.rej, якщо з’являться.
```

---

## Крок 3 — Rebuild і запуск gateway

```bash
cd /opt/microdao-daarion

# Зібрати образ gateway і запустити контейнер
docker compose -f docker-compose.node1.yml up -d --build gateway
```

Сервіс у compose називається `gateway`, контейнер — `dagi-gateway-node1`. Якщо використовуєте інший compose-файл або ім’я сервісу, підставте їх.

---

## Крок 4 — Перевірка health

```bash
# Локально на сервері
curl -s http://localhost:9300/health
curl -s http://localhost:9300/

# Ззовні (якщо порт 9300 відкритий)
curl -s http://144.76.224.179:9300/health
```

Очікується: HTTP 200 і JSON зі статусом сервісу.

---

## Крок 5 — Перевірка логів (NameError / ImportError)

```bash
docker logs dagi-gateway-node1 2>&1 | tail -100
docker logs dagi-gateway-node1 2>&1 | grep -E "NameError|ImportError|Stepan mode|Stepan inproc|Stepan disabled"
```

- Має з’явитися рядок типу `Stepan mode=inproc (AGX_STEPAN_MODE)` після першого звернення до AgroMatrix або при старті (залежно від реалізації логу).
- Не повинно бути `NameError: has_recent_interaction` або `ImportError` через crews/agromatrix_tools після коректного монтування та env.

Якщо бачите `Stepan disabled` — перевірте `AGX_REPO_ROOT`, наявність томів `crews` і `packages/agromatrix-tools` у compose та що в контейнері є відповідні каталоги.

---

## Крок 6 — Smoketests

1. **Doc follow-up (раніше 500)**  
   У Telegram-чаті агента AgroMatrix: надіслати документ, потім текстове питання по ньому.  
   Очікується: відповідь без HTTP 500 (відповідь з RAG або повідомлення про відсутність відповіді в документі).

2. **Оператор: /whoami**  
   Від користувача з `user_id` у `AGX_OPERATOR_IDS` (або в чаті з `AGX_OPERATOR_CHAT_ID`): надіслати `/whoami`.  
   Очікується: відповідь від Степана (whoami), не звичайний pipeline.

3. **Оператор: звичайний текст без slash**  
   Від того ж оператора: надіслати текст без слеша (наприклад «привіт»).  
   Очікується: обробка через Степана (handle_stepan_message), не fallback у звичайний Router pipeline.

4. **Не-оператор: звичайний текст**  
   Від користувача, який не входить до операторів: звичайне повідомлення.  
   Очікується: обробка стандартним Router pipeline, **не** через Степана.

Перед тестами 2–4 переконайтеся, що в env задані `AGX_OPERATOR_IDS` (та за потреби `AGX_OPERATOR_CHAT_ID`).

---

## Rollback

Якщо потрібно повернути стан до патчу:

```bash
cd /opt/microdao-daarion

# Скасувати зміни в робочій копії (повернути файли до останнього коміту)
git checkout HEAD -- gateway-bot/http_api.py gateway-bot/services/doc_service.py gateway-bot/app.py gateway-bot/gateway_boot.py crews/agromatrix_crew/operator_commands.py docker-compose.node1.yml
rm -f gateway-bot/gateway_boot.py

# Перезібрати і перезапустити gateway
docker compose -f docker-compose.node1.yml up -d --build gateway
```

Або ж повернути весь репо до попереднього коміту і перезібрати образ:

```bash
git reset --hard HEAD
docker compose -f docker-compose.node1.yml up -d --build gateway
```

---

## Env на НОДА1 (без секретів)

Переконайтеся, що в середовищі gateway (compose або .env) задані:

- `AGX_STEPAN_MODE=inproc` (за замовчуванням)
- `AGX_REPO_ROOT=/app` (в контейнері; на хості відповідає вашому `DEPLOY_ROOT` у путях томів)
- `AGX_OPERATOR_IDS=***` — список Telegram user_id операторів (через кому)
- `AGX_OPERATOR_CHAT_ID=***` — опційно, обмеження чату для операторів
- `OPENAI_API_KEY=***` — потрібен для inproc Степана

Секрети не виводьте в логи; у документації позначайте як `KEY=***`.

---

## A. Імпорт gateway_boot (fail-closed)

У контейнері CMD: `WORKDIR /app/gateway-bot` і `python -m uvicorn app:app`. Тому поточний каталог для імпортів — `/app/gateway-bot`. У `app.py` використовується **`import gateway_boot`** (модуль у тій самій директорії, що й `app.py`), тож Python резолвить `/app/gateway-bot/gateway_boot.py`. Це коректно при volume `${DEPLOY_ROOT}/gateway-bot:/app/gateway-bot:ro`.

**Швидкий тест у контейнері (CWD /app/gateway-bot, як у uvicorn):**
```bash
ssh root@144.76.224.179 'docker exec dagi-gateway-node1 sh -c "cd /app/gateway-bot && python3 -c \"import gateway_boot; print(\\\"gateway_boot OK\\\")\""'
```
Очікується: `gateway_boot OK`. Якщо тут `ImportError` — `STEPAN_IMPORTS_OK` ніколи не стане `True` і Степан залишиться тихо вимкненим.

---

## B. Volume-монтування crews і packages/agromatrix-tools

У `docker-compose.node1.yml` томи змонтовані **в контейнері** так:
- `${DEPLOY_ROOT:-.}/crews` → **`/app/crews`**
- `${DEPLOY_ROOT:-.}/packages/agromatrix-tools` → **`/app/packages/agromatrix-tools`**

На хості це може бути `/opt/microdao-daarion/crews`, але в контейнері завжди `/app/crews` та `/app/packages/agromatrix-tools`.

**Перевірка наявності в контейнері:**
```bash
ssh root@144.76.224.179 "docker exec dagi-gateway-node1 ls -la /app/crews | head"
ssh root@144.76.224.179 "docker exec dagi-gateway-node1 ls -la /app/packages/agromatrix-tools | head"
```
Очікується: список файлів/каталогів (наприклад `agromatrix_crew` у crews, пакет у agromatrix-tools). Якщо `No such file or directory` — томи не змонтовані або compose не оновлено після патчу.

---

## PYTHONPATH у контейнері (crews і agromatrix_tools)

Щоб працювало `from crews.agromatrix_crew.run import ...`, у `sys.path` має бути **`/app`** (батько каталога `crews`), а не `/app/crews`.  
Щоб працювало `import agromatrix_tools`, у `sys.path` має бути **`/app/packages/agromatrix-tools`** (батьківська директорія пакета).

У патчі задано: `PYTHONPATH=/app:/app/packages/agromatrix-tools`. При потребі можна додати `/app/gateway-bot`, але uvicorn і так додає робочу директорію при `python -m uvicorn app:app`.

**Практичний контроль після деплою:**
```bash
ssh root@144.76.224.179 "docker exec dagi-gateway-node1 sh -c 'env | grep -E \"^PYTHONPATH=\" || true'"
ssh root@144.76.224.179 "docker exec dagi-gateway-node1 sh -c 'cd /app/gateway-bot && python3 -c \"import sys; print(\\\"\\n\\\".join(sys.path))\"' | head -20"
```

---

## Читання STEPAN_IMPORTS_OK (без “заморожування”)

Щоб прапорець оновлювався після startup, усі місця мають робити **`import gateway_boot`** і читати **`gateway_boot.STEPAN_IMPORTS_OK`** (або `getattr(gateway_boot, "STEPAN_IMPORTS_OK", False)`).  
**Не використовувати** `from gateway_boot import STEPAN_IMPORTS_OK` — значення “заморозиться” на момент імпорту і не відобразиться після встановлення в `app.py`.

**Grep-контроль на сервері:**
```bash
ssh root@144.76.224.179 "cd /opt/microdao-daarion && grep -Rn 'from gateway_boot import' gateway-bot || true"
ssh root@144.76.224.179 "cd /opt/microdao-daarion && grep -Rn 'import gateway_boot' gateway-bot"
```
Очікується: немає збігів для `from gateway_boot import`; є лише `import gateway_boot` (у app.py та http_api.py).

---

## Compose env: лапки для AGX_OPERATOR_*

Якщо значення задані прямо в YAML (а не тільки через `${...}`), краще завжди брати їх у лапки, щоб уникнути проблем парсингу (зокрема від’ємні chat_id):
- `AGX_OPERATOR_CHAT_ID: "-1001234567890"`
- `AGX_OPERATOR_IDS: "123,456"`

При використанні змінних середовища (`AGX_OPERATOR_IDS=${AGX_OPERATOR_IDS:-}`) лапки ставлять у `.env` або в значенні змінної при експорті.

---

## Мінімальний фінальний чек-лист (після git apply і --build)

1. **Патч застосовано чисто**  
   Перед першим застосуванням: `git apply --check agromatrix_stepan_noda1_prod.patch` (код виходу 0 = можна застосовувати).  
   Після застосування можна перевірити наявність змін, наприклад:
   ```bash
   ssh root@144.76.224.179 "cd /opt/microdao-daarion && test -f gateway-bot/gateway_boot.py && grep -q 'import gateway_boot' gateway-bot/app.py && echo OK"
   ```

2. **Rebuild gateway**
   ```bash
   ssh root@144.76.224.179 "cd /opt/microdao-daarion && docker compose -f docker-compose.node1.yml up -d --build gateway"
   ```

3. **Stepan реально увімкнувся (логи)**
   ```bash
   ssh root@144.76.224.179 "docker logs dagi-gateway-node1 --since 10m 2>&1 | grep -E 'Stepan mode|STEPAN_IMPORTS_OK|Stepan disabled|Stepan inproc|ImportError|ModuleNotFoundError' | tail -30"
   ```
   Очікується: згадка `Stepan mode=inproc` (після першого звернення до AgroMatrix) і **відсутність** "Stepan disabled". У логах після старту має з’явитися рядок **`STEPAN_IMPORTS_OK=True`** (додано в app.py після встановлення прапорця), тоді fail-closed коректно пройшов.

4. **Оператори задані (обов’язково для "людського" режиму)**
   ```bash
   ssh root@144.76.224.179 "docker exec dagi-gateway-node1 env | grep -E '^AGX_OPERATOR_IDS=|^AGX_OPERATOR_CHAT_ID=' | sed 's/=.*/=***/'"
   ```
   Очікується: хоча б `AGX_OPERATOR_IDS=***` (значення приховуються). Якщо змінних немає — операторські повідомлення не підуть у Степана.

5. **Опційно: резолв gateway_boot і томи (див. блоки A і B вище).**

---

## Нюанс: AGX_STEPAN_MODE=http

Режим `AGX_STEPAN_MODE=http` зараз повертає 501 stub (Степан "офіційно" недоступний). Щоб у проді не випадково вимкнути Степана:
- У **docker-compose.node1.yml** у секції gateway залишити явно **`AGX_STEPAN_MODE=inproc`** (або не задавати — тоді default з патчу inproc).
- Режим `http` використовувати лише в тестових середовищах, коли з’явиться реалізація клієнта crewai-service.

---

## Фрагмент патчу для перевірки імпортів (app.py + gateway_boot)

Нижче — заголовки diff і контекст імпортів у `app.py`, щоб переконатися, що `gateway_boot` імпортується як модуль з тієї самої директорії, де лежить `app.py`, і резолвиться в контейнері.

**gateway-bot/app.py (фрагмент після патчу):**
```python
import logging
import os
import sys
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from http_api import router as gateway_router
from http_api_doc import router as doc_router

import gateway_boot   # <-- той самий каталог: /app/gateway-bot/gateway_boot.py
```

**gateway-bot/gateway_boot.py (новий файл):**
```python
"""
Boot-time state for Gateway. Set by app startup; read by http_api.
"""
STEPAN_IMPORTS_OK = False
```

У контейнері: `WORKDIR /app/gateway-bot`, volume монтує `gateway-bot` у `/app/gateway-bot`, тому `app.py` і `gateway_boot.py` лежать поруч — `import gateway_boot` коректно знаходить модуль. Після startup у `startup_stepan_check()` при успішному імпорті виконується `gateway_boot.STEPAN_IMPORTS_OK = True` і лог `STEPAN_IMPORTS_OK=True`; інакше прапорець залишається `False` (fail-closed). У **http_api** використовується лише `import gateway_boot` і `getattr(gateway_boot, "STEPAN_IMPORTS_OK", False)` — не `from gateway_boot import ...`, щоб значення читалося в runtime після оновлення в app.py.

---

## Підсумок перевірок “готово до прод”

Мінімально достатньо три пункти:

1. **Логи після старту** (Stepan увімкнено, без ImportError):
   ```bash
   ssh root@144.76.224.179 "docker logs dagi-gateway-node1 --since 10m 2>&1 | grep -E 'Stepan mode|STEPAN_IMPORTS_OK|Stepan disabled|ImportError|ModuleNotFoundError' | tail -120"
   ```

2. **Volumes на місці** (`/app/crews`, `/app/packages/agromatrix-tools`):
   ```bash
   ssh root@144.76.224.179 "docker exec dagi-gateway-node1 ls -la /app/crews | head"
   ssh root@144.76.224.179 "docker exec dagi-gateway-node1 ls -la /app/packages/agromatrix-tools | head"
   ```

3. **Env операторів і режиму** (масковано):
   ```bash
   ssh root@144.76.224.179 "docker exec dagi-gateway-node1 env | grep -E '^AGX_OPERATOR_IDS=|^AGX_OPERATOR_CHAT_ID=|^AGX_STEPAN_MODE=' | sed 's/=.*/=***/'"
   ```

Якщо ці три пункти зелені — патч відповідає fail-closed моделі і не повинен давати “тихих” падінь gateway при відсутніх crews/agromatrix-tools.
