133 lines
9.5 KiB
Markdown
133 lines
9.5 KiB
Markdown
# Sonnet Review: план миграции ВМ → Flask — анализ и ответ
|
||
|
||
**Дата:** 2026-07-14
|
||
**Источник:** Sonnet (Claude) — ревью `History/migration-vm-to-flask-plan-2026-07-14.md`
|
||
|
||
---
|
||
|
||
## Резюме Соннета
|
||
|
||
План **одобрен архитектурно**. Критичных блокеров — 2, важных дополнений — 3, улучшений — 5.
|
||
|
||
---
|
||
|
||
## Критичные проблемы (Соннет)
|
||
|
||
### 1. `site/` — конфликт с Python stdlib ⛔
|
||
|
||
> `from site.db import ...` — `site` это встроенный модуль Python. При запуске не из корня Python найдёт встроенный `site`, а не локальный.
|
||
|
||
**Моё мнение:** ✅ **Согласен полностью.** Это реальная проблема. `site` — built-in модуль Python, импортируется при старте интерпретатора. Если `PYTHONPATH` или `sys.path` поставит корень раньше чем `site-packages` — получим наш пакет. Если наоборот — stdlib. Нестабильно.
|
||
|
||
**Решение:** переименовать `site/` → `app/` или `contracts_app/`. Я за `app/` — короче, семантически понятно (Flask application factory), не конфликтует ни с чем.
|
||
|
||
### 2. `/api/cleanup` — нулевая аутентификация ⛔
|
||
|
||
> Любой может POST /api/cleanup → потеря всех данных.
|
||
|
||
**Моё мнение:** ✅ **Согласен.** Но с нюансом. Текущий `convert_server.py` тоже имеет этот эндпоинт без auth — и он на внешнем домене `contracts.kube5s.ru`. Так что это не регресс, а существующая дыра.
|
||
|
||
**Решение:**
|
||
- Минимум: `X-Api-Key` в заголовке, сверка с `os.environ["API_KEY"]`
|
||
- Средний: nginx `allow 127.0.0.1; deny all;` на этот location (если cleanup вызывается только с самого Flask-контейнера)
|
||
- Правильный: убрать cleanup вообще, заменить на автоочистку по TTL (cron/APScheduler)
|
||
|
||
Но это **отдельная задача**, не часть миграции. В рамках миграции — просто не ухудшить ситуацию.
|
||
|
||
---
|
||
|
||
## Важные дополнения (Соннет)
|
||
|
||
### 3. Файловый lock для classify — хрупкий
|
||
|
||
> `/tmp/classify_{batch_id}.lock` — если Flask упадёт, lock остаётся навсегда.
|
||
|
||
**Моё мнение:** ✅ **Согласен.** Lock-файл — антипаттерн в stateless-приложении. Сейчас в `convert_server.py` та же схема (subprocess + lock-файл), и она работает только потому что ВМ не рестартит.
|
||
|
||
**Решение:** in-memory `dict[batch_id] → thread` + проверка `thread.is_alive()`. При старте Flask никаких lock-файлов нет. Лучше: store `classify_status='processing'` в БД и сбрасывать `processing → pending` при старте приложения.
|
||
|
||
### 4. SSE — нет heartbeat
|
||
|
||
> При медленном LLM nginx и браузеры режут соединение.
|
||
|
||
**Моё мнение:** ✅ **Согласен.** Добавить `yield ": heartbeat\n\n"` раз в 15 секунд. SSE-комментарий (строка начинается с `:`) игнорируется EventSource, но сбрасывает таймауты nginx/браузера.
|
||
|
||
### 5. Старые proxy-роуты — явно не сказано удалить
|
||
|
||
> В плане написано «создать новый app.py», но не сказано явно «удалить старые /api/* proxy-роуты».
|
||
|
||
**Моё мнение:** ✅ **Согласен, но это очевидно.** Новый `app.py` полностью заменяет старый — старых роутов `/chat`, `/api/prompts` (proxy на ВМ) не будет, потому что логика теперь локальная. Но в плане стоит написать явно: «удалить ВСЕ proxy-роуты, они больше не нужны».
|
||
|
||
---
|
||
|
||
## Улучшения (Соннет)
|
||
|
||
### 6. `prompts_bp.py` и `pages_bp.py` — не раскрыты
|
||
|
||
**Моё мнение:** ✅ **Справедливо.** Но они тривиальны: `prompts_bp.py` = CRUD по таблице `prompts` (4 ручки), `pages_bp.py` = `render_template("index.html")` + `render_template("architect.html")`. Не расписывал детально потому что там нечего расписывать. Можно добавить один абзац для полноты.
|
||
|
||
### 7. `drhider/` — не упоминается
|
||
|
||
**Моё мнение:** ⚠️ **Справедливо, но осознанно.** `drhider/` — это отдельный сервис (обфускация персональных данных), не часть пайплайна сверки договоров. Он уже работает на Flask в `site/services/drhider.py` и `site/routes/drhider_bp.py`. В план миграции сверки он не входит — это другой сервис на том же хосте. Но стоит упомянуть это явно: «drhider не трогаем, он уже на Flask».
|
||
|
||
### 8. `repository.py` — не объяснено как расширить
|
||
|
||
**Моё мнение:** ⚠️ **Справедливо, но слишком рано.** Protocol-паттерн в `repository.py` уже есть, `PgRepository` частично реализован. Полное внедрение DI через репозиторий во все services — это Фаза 2, после того как базовая миграция заработает. Сейчас services используют `from site.db import documents` напрямую, и это ОК для первого шага. Переписывать всё на DI сразу = риск сломать работающий код.
|
||
|
||
**План:** после миграции — отдельная задача «внедрить Repository DI во все services».
|
||
|
||
### 9. Graceful shutdown — in-flight classify потоки
|
||
|
||
**Моё мнение:** ✅ **Справедливо.** При `docker stop` daemon-потоки убиваются, документы застревают в `classify_status='processing'`.
|
||
|
||
**Решение (уже в коде ВМ!):** `db/documents.reset_classify_status(batch_id)` сбрасывает `processing → pending`. Добавить вызов в `app.py` при старте:
|
||
|
||
```python
|
||
# При старте: сбросить все застрявшие processing → pending
|
||
from site.db.connection import execute
|
||
execute("UPDATE documents SET classify_status='pending', error_message=NULL WHERE classify_status='processing'")
|
||
```
|
||
|
||
### 10. Тесты — только curl, нет pytest
|
||
|
||
**Моё мнение:** ✅ **Справедливо.** В репо уже есть `tests/` с `conftest.py`. Нужен шаг «адаптировать тесты под `app.test_client()`». Но это **не блокер для миграции** — тесты можно дописать после.
|
||
|
||
---
|
||
|
||
## Моя итоговая оценка
|
||
|
||
| Категория | Пунктов | Критичность |
|
||
|-----------|---------|-------------|
|
||
| Критичные (надо исправить ДО миграции) | 2 | `site/` → `app/`, auth на cleanup |
|
||
| Важные (надо добавить в план) | 3 | lock→in-memory, heartbeat, явное удаление proxy |
|
||
| Улучшения (можно после миграции) | 5 | prompts/pages детали, drhider, repository DI, graceful, тесты |
|
||
|
||
### Что делаем ДО начала миграции:
|
||
|
||
1. **Переименовать `site/` → `app/`** — везде в плане, во всех импортах
|
||
2. **Добавить auth на `/api/cleanup`** — `X-Api-Key` или `require_local`
|
||
|
||
### Что добавляем в план:
|
||
|
||
3. **in-memory lock** для classify вместо файлового
|
||
4. **SSE heartbeat** раз в 15 сек
|
||
5. **Упомянуть явно:** старые proxy-роуты удалить, drhider не трогать
|
||
6. **Graceful shutdown:** сброс `processing → pending` при старте
|
||
7. **prompts_bp / pages_bp** — один абзац что там
|
||
8. **repository DI** — отложить на пост-миграцию
|
||
|
||
### Что НЕ блокер:
|
||
|
||
- `drhider` — уже на Flask, не часть миграции
|
||
- Repository DI — Фаза 2
|
||
- pytest — после миграции
|
||
- prompts/pages — тривиальны
|
||
|
||
---
|
||
|
||
## Вердикт
|
||
|
||
План **рабочий**. Соннет подтвердил архитектурные решения (blueprints, разделение db/services/routes, генератор для SSE, DI). Критичные замечания — реальные и требуют правки плана. Остальное — улучшения надёжности, которые можно добавить в план сейчас или отложить.
|
||
|
||
**Готовность к миграции:** 90%. После правки 2 критичных пунктов → можно начинать.
|