MpakoImageAIbot/checklist.md

402 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Checklist реализации MpakoImageAIbot
Источник: `plan.md`. Бот на `python-telegram-bot` v21+ (async) генерирует изображения через удалённый ComfyUI API, ведёт глобальную очередь, показывает позицию/прогресс и хранит запросы в SQLite.
Блоки идут в порядке зависимостей. В конце каждого блока — критерий готовности (DoD).
## Текущий статус
- Реализованы блоки 2–7, базовое хранилище (9), файлы Docker (10), тесты (11), README (12), веб-история (13), улучшение промптов (14), модерация (15), оплата Stars (16), баланс/рефералы (17), управление пользователями (18) и возрастной gate (19).
- Вживую проверены: T2I (Z-Image), I2I (Krea2), WebSocket-прогресс, улучшение промптов, Docker, CI и веб-история.
- 52 теста проходят: БД, workflows, очередь, веб-история, enhancer, модерация, платежи, премиум-очередь, баланс/рефералы, управление пользователями.
- Не проверено: реальная оплата Stars и реферальная выплата в Telegram, очередь из 3+ пользователей, падение ComfyUI, рестарт с `running`-задачей, детекция лица на реальном фото.
## 0. Допущения и решения по умолчанию
| Вопрос | Решение по умолчанию |
|---|---|
| HTTP-клиент | `httpx.AsyncClient`, без блокирующего `requests` |
| Очередь | Собственная очередь в приложении, по умолчанию 1 воркер; в ComfyUI уходит не более 1 задачи одновременно |
| Прогресс | WebSocket `/ws` + fallback-опрос `/history/{prompt_id}` |
| Один активный запрос на пользователя | Проверка в памяти + частичный уникальный индекс в SQLite |
| Задача в статусе `running` при рестарте | Пометить `failed` (не перезапускать автоматически) |
| Задачи `queued` при рестарте | Восстановить из БД и поставить в очередь заново |
| i2i без подписи | Отклонить и попросить промпт (не использовать дефолтный) |
| Чаты | Приватные; `user_id` считается id чата |
| Workflow | JSON в API-формате (экспорт «Save (API Format)» из ComfyUI) |
| T2I-модель | Z-Image Turbo GGUF, `EmptySD3LatentImage`, 1024×1024, 8 steps, cfg 1, denoise 1.0 |
| I2I-модель | Krea2 edit GGUF, `LoadImage`, 8 steps, cfg 1 |
| ComfyUI | `http://192.168.31.113:8188`, v0.40.0, RTX 4060 Ti 8 GB |
| Язык интерфейса | Русский |
Согласовано: **1 воркер**, задача `running` при рестарте → **`failed`**, workflow готовим вместе (см. блок 1). Для i2i используем **Krea2 edit**.
## 1. Подготовка workflow и окружения (до кода)
### 1.1 Экспорт workflow
- [x] Открыть ComfyUI, включить **Settings → Dev mode Options**, чтобы появился пункт **Save (API Format)**.
- [x] Собрать t2i-граф: `UnetLoaderGGUF → CLIPLoaderGGUF → CLIPTextEncode` + `EmptySD3LatentImage → KSampler → VAEDecode → SaveImage`.
- [x] Собрать i2i-граф: `LoadImage → TextEncodeQwenImageEdit → KSamplerAdvanced → VAEDecode → SaveImage`.
- [x] Экспортировать оба графа кнопкой **Save (API Format)** в `workflows/txt2img.json` и `workflows/img2img.json`.
- [x] Убедиться, что в JSON только API-узлы (`class_type`, `inputs`), без UI-метаданных и без `extra`-полей.
- [x] Определить ID нужных узлов. Удобно: выделить узел → `Properties` → `Node ID`, либо найти по `class_type` в JSON (`CLIPTextEncode`, `KSampler`, `LoadImage`).
- [x] Заполнить `workflows/mapping.json` (ID соответствуют текущим шаблонам):
```json
{
"txt2img": {
"positive": "67",
"negative": "71",
"seed": "70",
"latent": "81",
"save": "9"
},
"img2img": {
"positive": "25",
"negative": "10",
"seed": "3",
"input_image": "20",
"save": "24"
}
}
```
- [x] Проверить t2i вручную: отправить JSON на `POST /prompt` и получить `prompt_id`, затем забрать результат через `/history` и `/view`.
- [x] Проверить i2i: загрузить тестовое фото через `/upload/image`, подставить имя в `LoadImage`, выполнить генерацию.
- [ ] Проверить i2i с фото произвольного размера (портрет/ландшафт); при необходимости добавить `ImageScale` в граф.
- [x] Зафиксировать дефолтные `steps`, `cfg`, `denoise`, negative prompt и размер изображения.
### 1.2 Окружение
- [x] Убедиться, что Python 3.10+ доступен локально и в Docker-образе.
- [x] Проверить сетевую доступность ComfyUI: `GET http://IP_WINDOWS:PORT/system_stats`.
- [ ] Получить токен бота у `@BotFather`.
- [ ] Определить значения лимитов: максимальная длина промпта, максимальный размер фото, размер очереди, таймаут генерации.
- [ ] Решить, где будет запускаться бот (локально / Docker) и как он видит ComfyUI (LAN, VPN, `host.docker.internal`).
**DoD:** есть токен, два валидных workflow JSON, заполненный `mapping.json`; тестовый `POST /prompt` возвращает `prompt_id`, результат скачивается. T2I проверен (~52 с), i2i — (~61 с).
## 2. Каркас проекта и конфигурация
- [ ] Создать структуру:
```text
.
├── bot.py
├── comfy_client.py
├── db.py
├── queue_manager.py
├── config.py
├── workflows/
│ ├── txt2img.json
│ ├── img2img.json
│ └── mapping.json
├── storage/ # создаётся автоматически, в .gitignore
├── data/ # SQLite, в .gitignore
├── tests/
├── requirements.txt
├── requirements-dev.txt
├── Dockerfile
├── .dockerignore
├── .env.example
├── .gitignore
└── README.md
```
- [ ] `requirements.txt`: `python-telegram-bot`, `httpx`, `aiosqlite`, `websockets`, `python-dotenv` (версии — диапазонами, например `>=21,<23`).
- [ ] `requirements-dev.txt`: `pytest`, `pytest-asyncio`, `respx`.
- [ ] `config.py`: dataclass/`Settings`, загрузка `.env`, валидация обязательных полей, вычисление WS-URL из base URL.
- [ ] Переменные окружения: `BOT_TOKEN`, `COMFY_BASE_URL`, `WORKFLOW_T2I`, `WORKFLOW_I2I`, `WORKFLOW_MAPPING`, `STORAGE_DIR`, `DB_PATH`, `WORKER_CONCURRENCY=1`, `MAX_QUEUE_SIZE`, `ALLOWED_USER_IDS`, `PROGRESS_INTERVAL`, `GENERATION_TIMEOUT`, `LOG_LEVEL`.
- [ ] Настроить логирование в stdout: уровень из env, формат с timestamp/level/logger, никогда не логировать токен и содержимое `.env`.
- [ ] `.env.example` со всеми переменными и комментариями.
- [ ] `.gitignore`: `.env`, `storage/`, `data/`, `__pycache__/`, `.pytest_cache/`.
- [ ] Проверить импорт конфига и падение с понятной ошибкой при отсутствии `BOT_TOKEN`.
**DoD:** `python -c "from config import get_settings; print(get_settings())"` работает; секреты не попадают в репозиторий.
## 3. Слой хранения (`db.py`)
- [ ] Реализовать `init_db()`: подключение `aiosqlite`, `PRAGMA journal_mode=WAL`, `PRAGMA busy_timeout`, `PRAGMA foreign_keys=ON`, `row_factory`.
- [ ] Создать таблицу `requests` по плану:
```sql
CREATE TABLE IF NOT EXISTS requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
timestamp TEXT NOT NULL,
prompt_text TEXT NOT NULL,
input_file_path TEXT,
output_file_path TEXT,
status TEXT NOT NULL DEFAULT 'queued'
CHECK (status IN ('queued', 'running', 'done', 'failed')),
comfy_prompt_id TEXT
);
```
- [ ] Добавить расширения схемы (согласовать): `mode TEXT NOT NULL CHECK (mode IN ('t2i','i2i'))`, `error_text TEXT`, `finished_at TEXT`, `seed INTEGER`.
- [ ] Индексы: `(user_id, status)`, `(status, id)`.
- [ ] Частичный уникальный индекс «один активный запрос на пользователя»:
```sql
CREATE UNIQUE INDEX IF NOT EXISTS uq_requests_one_active
ON requests(user_id) WHERE status IN ('queued', 'running');
```
- [ ] CRUD: `create_request`, `get_request`, `get_active_by_user`, `set_status`, `set_comfy_prompt_id`, `set_output_path`, `set_error`, `list_queued`, `count_ahead`, `mark_running_as_failed`.
- [ ] Все временные метки — ISO 8601 UTC (`datetime.now(timezone.utc).isoformat()`).
- [ ] Атомарность: создание запроса и проверка активного — в одной транзакции; при конфликте unique-индекса возвращать понятную ошибку `UserAlreadyQueued`.
- [ ] Восстановление при старте: `queued` читаются по `id`, `running` → `failed` с текстом «прервано перезапуском».
- [ ] Тесты: создание, смена статусов, запрет второго активного запроса, выборка очереди, восстановление.
**DoD:** тесты БД зелёные; повторный `init_db()` не ломает существующую БД.
## 4. Клиент ComfyUI (`comfy_client.py`)
- [ ] Класс `ComfyClient` с ленивой инициализацией `httpx.AsyncClient` и явным `close()`.
- [ ] `submit_workflow(workflow, client_id) -> prompt_id`: `POST /prompt` с телом `{"prompt": workflow, "client_id": client_id}`.
- [ ] Обработка ответа `/prompt`: HTTP-ошибки, `error`, `node_errors` — поднимать `ComfyError` с человекочитаемым текстом.
- [ ] `upload_image(path, request_id) -> image_ref`: `POST /upload/image` (multipart), уникальное имя файла, `type=input`; вернуть имя/подпапку для `LoadImage`.
- [ ] `get_history(prompt_id) -> dict`: `GET /history/{prompt_id}`; извлекать список изображений из `outputs[*]["images"]`.
- [ ] `download_image(filename, subfolder, type, dest_path)`: `GET /view` потоково, запись через временный файл + `os.replace`.
- [ ] `wait_for_result(prompt_id, timeout)`: fallback-опрос истории с интервалом 2 с; таймаут → `ComfyTimeout`.
- [ ] WebSocket-прогресс: `ws://.../ws?clientId=...`, подключение **до** `POST /prompt`, чтобы не потерять ранние события.
- [ ] Разбор событий: `progress` (`value`/`max`, при наличии `prompt_id` — фильтровать), `executing`, `execution_success`, `execution_error`, `status`.
- [ ] Переподключение WS с backoff; при недоступности WS — не падать, переходить на polling.
- [ ] Таймауты: connect 10 с, запросы 30 с, ожидание генерации — `GENERATION_TIMEOUT`.
- [ ] Не ретраить слепо `POST /prompt` (риск дублей); ретраить только идемпотентные GET/upload.
- [ ] Типы ошибок: `ComfyConnectionError`, `ComfyHTTPError`, `ComfyExecutionError`, `ComfyTimeout`.
- [ ] Тесты с `respx`/fake-сервером: успешный submit, `node_errors`, таймаут, скачивание, парсинг progress.
**DoD:** отдельный скрипт/тест отправляет тестовый workflow на реальный ComfyUI, получает `prompt_id`, progress и сохраняет картинку.
## 5. Шаблоны workflow
- [x] Положить `txt2img.json` и `img2img.json` в `workflows/`.
- [x] `workflows/mapping.json`: соответствие «роль узла → node id» для каждого workflow (positive, negative, seed, latent/input_image).
- [ ] Валидация mapping при старте: все указанные узлы существуют, у них ожидаемые поля; иначе — падение с понятной ошибкой.
- [ ] `build_txt2img(prompt, negative, seed) -> dict`: глубокое копирование шаблона, подстановка prompt/negative/seed.
- [ ] `build_img2img(prompt, negative, seed, image_name) -> dict`: то же + имя загруженного изображения.
- [ ] Генерация seed: `secrets.randbelow(2**32)`, сохранять использованный seed в БД.
- [ ] Обрезка/нормализация промпта (strip, лимит длины), значение negative prompt по умолчанию из конфига.
- [ ] Гарантировать, что исходный шаблон не мутируется между запросами.
- [ ] Тесты: подстановка значений, отсутствующий узел, отсутствующий файл workflow.
**DoD:** юнит-тесты подтверждают корректную подстановку; при старте приложения шаблоны и mapping валидны.
## 6. Очередь и менеджер задач (`queue_manager.py`)
- [ ] `QueueManager` с зависимостями: `db`, `ComfyClient`, `WorkflowBuilder`, `Settings`.
- [ ] Внутренняя очередь `collections.deque` + `asyncio.Event` (не `asyncio.Queue`, чтобы уметь считать позицию).
- [ ] `asyncio.Lock` для всех операций с очередью и реестром активных пользователей.
- [ ] `enqueue(user_id, mode, prompt, input_path) -> EnqueueResult(request, position)`:
- [ ] проверить активный запрос пользователя (память + БД);
- [ ] проверить `MAX_QUEUE_SIZE`;
- [ ] создать запись в БД, добавить id в очередь, вернуть позицию.
- [ ] Позиция: количество задач впереди + выполняющаяся задача; считать уникальных пользователей (по плану — «X человек»).
- [ ] Воркер: цикл `while not stopping`, брать задачу, никогда не умирать из-за ошибки одной задачи.
- [ ] Жизненный цикл задачи: `queued → running → done/failed`; обновлять `comfy_prompt_id`, `output_file_path`, `error_text`, `finished_at`.
- [ ] Для i2i: upload изображения → подстановка имени → submit.
- [ ] Уникальный `client_id` на задачу; WS-подключение до submit, закрытие после завершения.
- [ ] Прогресс: throttle (не чаще `PROGRESS_INTERVAL`, шаг ≥ 5%), вычисление процента из `value/max`.
- [ ] События наружу через callback/event bus: `request_started`, `progress`, `request_done`, `request_failed` — без импорта Telegram в менеджере.
- [ ] Обработка ошибок: любое исключение → `failed`, лог с `request_id`, событие для бота.
- [ ] `start()`: восстановить `queued` из БД, пометить `running` как `failed`, запустить воркер.
- [ ] `stop()`: остановить приём, дождаться воркера, пометить незавершённые задачи, закрыть клиент.
- [ ] Тесты с fake-клиентом: порядок FIFO, позиции, дубль пользователя, ошибка генерации, отмена при остановке.
**DoD:** два пользователя получают корректные позиции; повторная заявка отклоняется; прогресс доходит до подписчика; после рестарта `queued` восстанавливаются.
## 7. Telegram-слой (`bot.py`)
- [ ] Инициализация `Application` (PTB v21+, async), регистрация `post_init`/`post_shutdown` для старта/остановки `QueueManager`.
- [ ] Команды `/start` и `/help`: краткая инструкция по двум режимам и формату ввода.
- [ ] (Опционально) `/status`: позиция и статус текущего запроса.
- [ ] Текстовый хендлер (`filters.TEXT & ~filters.COMMAND`): создать t2i-запрос, поставить в очередь.
- [ ] Фото-хендлер (`filters.PHOTO`): взять `photo[-1]`, скачать через `get_file().download_to_drive()`, сохранить в `storage/{user_id}/`.
- [ ] Для i2i: caption обязателен; пустой caption → подсказка «пришлите фото с описанием».
- [ ] Валидация: пустой/слишком длинный промпт, размер фото, поддерживаемые форматы; лимит caption 1024 символа.
- [ ] Ответ при дубле: «Ты уже в очереди» + текущая позиция (по плану).
- [ ] Ответ при постановке: «Принято. Перед тобой X человек» / «Ты первый, начинаю…».
- [ ] Статусное сообщение: сохранить `message_id` в памяти; редактировать при `request_started` («Генерация… 0%») и `progress`.
- [ ] Throttle редактирования статуса (не чаще 3 с), игнорировать `Message is not modified`.
- [ ] По завершении: отправить результат как фото (`FSInputFile`), статусное сообщение удалить или заменить на «Готово».
- [ ] По ошибке: редактировать статус на понятный текст, логировать техническую причину с `request_id`.
- [ ] (Опционально) `ALLOWED_USER_IDS`: отказ остальным.
- [ ] (Опционально) `ChatAction.UPLOAD_PHOTO` при отправке результата.
- [ ] Аккуратно обрабатывать `TelegramError`/`BadRequest`, не ронять приложение.
- [ ] Проверить, что команды не перехватываются текстовым хендлером.
**DoD:** вручную проходят сценарии t2i и i2i; при дубле приходит корректный отказ; прогресс обновляется без флуда.
## 8. Сквозная интеграция
- [ ] Связать `bot.py → QueueManager → db → ComfyClient → storage`.
- [ ] t2i: текст → картинка → файл в `storage/{user_id}/` → отправка в чат → `done` в БД.
- [ ] i2i: фото + caption → upload → workflow → картинка → отправка → `done`.
- [ ] Очередь из 3 пользователей: позиции 1/2/3, затем корректное продвижение.
- [ ] ComfyUI недоступен: задача → `failed`, пользователь получает понятную ошибку, воркер жив.
- [ ] Ошибка выполнения workflow (`execution_error`): сообщение пользователю без стектрейса.
- [ ] Рестарт с задачами в очереди: `queued` продолжают выполняться, `running` помечены `failed`.
- [ ] Повторный запуск после падения: БД и файлы консистентны.
- [ ] Проверить отсутствие блокировки event loop (все I/O — async).
**DoD:** все сценарии выше пройдены на реальном ComfyUI.
## 9. Файловое хранилище
- [ ] Структура: `storage/{user_id}/{request_id}_input.<ext>`, `storage/{user_id}/{request_id}_output.<ext>`.
- [ ] Безопасные имена (только сгенерированные, не из пользовательского ввода), защита от path traversal.
- [ ] Атомарная запись: `.part` → `os.replace`.
- [ ] Создание директорий при старте/записи.
- [ ] (Опционально) очистка файлов старше N дней по расписанию; настройка `FILE_RETENTION_DAYS`.
- [ ] (Опционально) проверка свободного места и понятная ошибка при переполнении.
- [ ] Не хранить бинарные данные в SQLite — только пути.
**DoD:** входные и выходные файлы лежат по ожидаемым путям; повторный запуск не конфликтует по именам.
## 10. Docker и деплой
- [x] `Dockerfile` на `python:3.13-slim`, непривилегированный пользователь, `PYTHONUNBUFFERED=1`.
- [x] Копирование кода и установка `requirements.txt`; без лишних build-зависимостей.
- [x] `.dockerignore`: `.env`, `storage/`, `data/`, `__pycache__/`, `tests/`, `.git/`.
- [x] `docker-compose.yml`: `env_file: .env`, volume для `./storage` и `./data`, `restart: unless-stopped`.
- [x] `docker-compose.yml` использует готовый образ `mpakoimageaibot:latest`.
- [x] Gitea Actions: `.gitea/workflows/docker-build.yml` собирает и пушит `mpakoimageaibot:latest` и `:<sha>` в Registry при push в `main`/`master`.
- [x] Сетевой доступ к ComfyUI: `host.docker.internal` для Windows/macOS или LAN-IP для Linux; зафиксировать в README.
- [x] Проверить, что SQLite и storage переживают пересоздание контейнера.
- [ ] (Опционально) healthcheck.
- [x] Логи только в stdout/stderr.
**DoD:** `docker compose up -d` поднимает бота, генерация работает, данные сохраняются после `down/up`.
## 11. Тесты и приёмка
- [ ] `pytest` + `pytest-asyncio`, общая фикстура временной БД и storage.
- [ ] Юнит-тесты: `db.py`, подстановка workflow, расчёт позиции, throttle прогресса, парсинг WS-событий.
- [ ] Интеграционный тест: fake ComfyUI (HTTP + WS) → полный цикл `enqueue → running → done`, файл создан.
- [ ] Тест ошибок: ComfyUI 500, `node_errors`, таймаут, обрыв WS.
- [ ] Тест конкуренции: одновременные сообщения одного пользователя → только один запрос.
- [ ] Ручной e2e на реальном ComfyUI (t2i, i2i, очередь, рестарт).
- [ ] Проверка лимитов Telegram: размер результата, длина caption, частота редактирований.
**DoD:** тесты зелёные; ручной чек-лист из блока 8 пройден.
## 12. Документация
- [ ] `README.md`: описание, схема работы, требования.
- [ ] Инструкция по экспорту workflow в API-формате и заполнению `mapping.json`.
- [ ] Таблица переменных окружения с примерами.
- [ ] Запуск локально и через Docker.
- [ ] Раздел troubleshooting: ComfyUI недоступен, WS не подключается, «ты уже в очереди», ошибки workflow.
- [ ] Краткое описание архитектуры и границ MVP.
**DoD:** новый разработчик поднимает проект по README без устных пояснений.
## 13. Веб-история
- [x] `web.py`: FastAPI + Jinja2, отдельный контейнер `web`.
- [x] Список запросов с фильтрами `user_id`, `status`, `mode` и пагинацией.
- [x] Отображение `@username` с fallback на Telegram ID; миграция старых БД.
- [x] Превью результата в списке, генерация и кэш в `storage/.thumbnails`.
- [x] Карточка запроса: промпт, seed, ошибка, `comfy_prompt_id`, вход/результат.
- [x] Отдача изображений только из `STORAGE_DIR` (защита от path traversal).
- [x] Доступ только из локальной сети: публикация на `WEB_BIND_IP`, без авторизации.
- [x] Тесты: фильтры БД, страницы, media.
- [x] Compose-сервис на порту `8099` → `8080` контейнера.
**DoD:** история открывается по `http://IP:8099`, фильтр по пользователю работает, изображения доступны.
## 14. Улучшение промптов
- [x] `workflows/prompt_enhance.json`: `CLIPLoader` + `TextGenerate` + `PreviewAny` на существующей `qwen3vl_4b`.
- [x] `prompt_enhancer.py`: ChatML-промпт для T2I/I2I, очистка ответа.
- [x] Улучшение до генерации, с fallback на исходный промпт.
- [x] Исходный и улучшенный промпты хранятся в БД, отображаются в веб-истории.
- [x] Событие «Улучшаю промпт…» в статусе бота.
- [x] Тесты enhancer, очереди и миграции БД.
**DoD:** русский запрос превращается в английский промпт, изображение генерируется по нему; при сбое LLM используется оригинал.
## 15. Фильтрация промптов
- [x] `is_prompt_allowed`: нормализация, hard/context-блоки, обходные паттерны.
- [x] Админы из `ADMIN_USER_IDS` обходят фильтр.
- [x] Логирование блокировок в `blocked_requests`.
- [x] Ответ «Запрос нарушает правила. Звёзды не списаны.».
- [x] i2i: OpenCV-детекция при наличии; иначе консервативный ручной флаг.
- [x] Тесты правил и очереди.
**DoD:** небезопасный промпт не попадает в очередь, причина пишется в БД, админ может отправлять без фильтра.
## 16. Оплата Stars
- [x] `PAYMENTS_ENABLED` / `STARS_PRICE`, админы бесплатно.
- [x] Премиум-очередь: `PREMIUM_STARS_PRICE`, чередование с обычной, счётчики очередей до оплаты.
- [x] Invoice `XTR` + `pre_checkout_query` + `successful_payment`.
- [x] `pending_payments`: payload, сумма, статус, charge_id, request_id.
- [x] Возврат звёзд при ошибке постановки в очередь и генерации.
- [x] `payment_charge_id` и `payment_refunded` в `requests`.
- [x] Тесты жизненного цикла платежа.
**DoD:** не-админ получает счёт на 5 звёзд, после оплаты задача ставится в очередь; при сбое звёзды возвращаются.
## 17. Баланс и рефералы
- [x] `users`: 3 стартовые генерации, ежедневное +1 в 00:00 МСК.
- [x] Списание 1 генерации за обычную очередь, премиум — только звёзды.
- [x] `/balance`, `/invite`, реферальная ссылка `?start=ref_<id>`.
- [x] +3 генерации рефереру за первую оплату друга и уведомление.
- [x] Тесты баланса, ежедневного начисления и рефералов.
**DoD:** новый пользователь получает 3 генерации, ежедневно +1, друг после первой оплаты приносит рефереру +3.
## 18. Управление пользователями в веб
- [x] Страница `/users`: поиск, баланс, админ-флаг, блокировка.
- [x] POST-действия: изменить баланс, назначить/снять админа, заблокировать.
- [x] Блокировка проверяется ботом при запросах, оплате и callback-выборе.
- [x] Админы из env сидируются один раз; дальше источник истины — БД.
- [x] Тесты страницы и действий.
**DoD:** изменения в веб-интерфейсе применяются к боту без рестарта.
## 19. Подтверждение 18+
- [x] Возрастной gate в `/start` с кнопками «18+»/«меньше 18».
- [x] Флаг `age_confirmed`/`age_declined` в `users`.
- [x] Без подтверждения запросы, оплата и выбор очереди недоступны.
- [x] Существующие пользователи проходят подтверждение один раз.
- [x] Статус 18+ отображается в веб-управлении пользователями.
**DoD:** несовершеннолетний пользователь не может пользоваться ботом; подтверждение сохраняется.
## Порядок работ
1. Блок 1 (подготовка) — без него нельзя валидировать клиент и workflow.
2. Блок 2 (каркас) → Блок 3 (БД) → Блок 4 (ComfyUI-клиент) → Блок 5 (workflow).
3. Блок 6 (очередь) → Блок 7 (бот) → Блок 8 (интеграция).
4. Блоки 9 (storage) и 10 (Docker) можно делать параллельно после блока 2.
5. Блоки 11 (тесты) и 12 (документация) — параллельно с 6–10, финализация после 8.
## Открытые вопросы
Согласовано: 1 воркер; `running` при рестарте → `failed`; T2I — Z-Image, i2i — Krea2 edit. Осталось уточнить:
1. i2i без подписи: отклонять (по умолчанию) или использовать фиксированный промпт?
2. Нужны ли `/cancel`, удаление из очереди и ограничение доступа `ALLOWED_USER_IDS` в MVP?
3. Сколько хранить входные/выходные файлы? Нужна ли автоочистка?
4. Где будет хоститься бот и как он видит ComfyUI (LAN/VPN/интернет)?
5. Добавлять ли в схему `mode`, `seed`, `error_text`, `finished_at` (сейчас они предложены как расширение плана)?
6. Уведомлять ли пользователя, если его `running`-задача была помечена `failed` после рестарта?
7. Нужно ли масштабировать входное фото перед i2i (сейчас подаётся как есть; проверен только квадрат 1024×1024)?
## Вне скоупа MVP
- Платежи, подписки, квоты.
- Несколько моделей/чекпоинтов и выбор модели пользователем.
- Галерея, история генераций в боте, повтор генерации.
- LoRA/ControlNet и другие продвинутые workflow.
- Горизонтальное масштабирование и несколько экземпляров бота на одной SQLite.
- Админ-панель.