From 8da39ebae17cd1e0970c218db24b3f64b59bbe5d Mon Sep 17 00:00:00 2001 From: febux Date: Sat, 5 Sep 2026 00:25:25 +0300 Subject: [PATCH] Bot setup was fixed with service profile and update docs. --- .env.bot.example | 11 +++--- Makefile | 74 +++++++++++++++++++++++++++----------- README.md | 14 ++++---- docker-compose.bot.yml | 28 ++++++++++++--- docker-compose.yml | 22 ++++++------ docs/ARCHITECTURE.md | 16 ++++++--- docs/DEPLOY.md | 80 +++++++++++++++++++++++++++++------------- docs/TICKETS.md | 9 ++--- 8 files changed, 174 insertions(+), 80 deletions(-) diff --git a/.env.bot.example b/.env.bot.example index d299fe2..db29601 100644 --- a/.env.bot.example +++ b/.env.bot.example @@ -20,8 +20,9 @@ BOT_SERVICE_TOKEN= # Example: https://contract-check.example.com API_URL= -# Optional: Redis URL for shared rate-limit state. -# Leave empty for the in-memory backend (recommended for a single bot instance). -# If set, Redis must be reachable from this host over a private network/VPN; -# never expose Redis to the public internet. -REDIS_URL= +# Redis URL for rate-limit state. +# Default in docker-compose.bot.yml is the local Redis container (redis://redis:6379/0). +# Set to empty to use the in-memory backend (fine for a single bot instance). +# If you point this at an external Redis, it must be reachable over a private +# network/VPN; never expose Redis to the public internet. +REDIS_URL=redis://redis:6379/0 diff --git a/Makefile b/Makefile index 8be2a9a..dc5ac71 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,16 @@ # Usage: make (see `make help`) .PHONY: help install lint lint-fix isort isort-check typecheck test test-unit test-integration migrate \ - infra-up infra-down infra-logs services-up services-down services-logs \ - services-obs obs-up obs-down obs-logs obs-url obs-reset \ - api api-logs bot bot-logs worker-extract worker-analyze worker-notify \ + infra-up infra-down infra-logs services-up services-down services-logs services-ps \ + full-up full-down full-logs full-ps \ + services-obs services-observer services-nginx \ + obs-up obs-down obs-logs obs-url obs-reset \ + observer-up observer-down observer-logs \ + nginx-up nginx-down nginx-logs nginx-ps \ + api api-logs bot bot-up bot-down bot-logs bot-ps worker-extract worker-analyze worker-notify \ + bot-remote-up bot-remote-down bot-remote-logs bot-remote-ps \ seed-token jwt-secret jwt-token jwt-verify health shell-api shell-bot \ - shell-db admin-promote admin-list clean + shell-db admin-promote admin-list clean dev dev-obs stop stop-obs # ───────────────────────────────────────────────────────────────────────────── # Help @@ -64,28 +69,46 @@ infra-logs: ## Tail infra logs docker compose logs -f # ───────────────────────────────────────────────────────────────────────────── -# Docker: all services (api + workers + bot) +# Docker: api + workers (no bot) # ───────────────────────────────────────────────────────────────────────────── -services-up: ## Start all services (needs infra running) +services-up: ## Start api + worker services (needs infra running; no bot) docker compose --profile services up -d --build --remove-orphans -services-down: ## Stop all services +services-down: ## Stop api + worker services docker compose --profile services down -services-logs: ## Tail all service logs +services-logs: ## Tail api + worker logs docker compose --profile services logs -f -services-ps: ## Show running containers +services-ps: ## Show api + worker containers docker compose --profile services ps -services-obs: ## Start services + Grafana/Loki/Prometheus observability - docker compose --profile services --profile obs up -d --build --remove-orphans +# ───────────────────────────────────────────────────────────────────────────── +# Docker: full stack (api + workers + bot on the same host) +# ───────────────────────────────────────────────────────────────────────────── +full-up: ## Start full stack: api + workers + bot (needs infra running) + docker compose --profile services --profile bot up -d --build --remove-orphans -services-observer: ## Start services + OpenObserve collector stack - docker compose --profile services --profile observer up -d --build --remove-orphans +full-down: ## Stop full stack: api + workers + bot + docker compose --profile services --profile bot down -services-nginx: ## Start with nginx reverse proxy - docker compose --profile services --profile edge up -d --build --remove-orphans +full-logs: ## Tail full stack logs (api + workers + bot) + docker compose --profile services --profile bot logs -f + +full-ps: ## Show full stack containers (api + workers + bot) + docker compose --profile services --profile bot ps + +# ───────────────────────────────────────────────────────────────────────────── +# Docker: combined with observability / edge +# ───────────────────────────────────────────────────────────────────────────── +services-obs: ## Start api + workers + bot + Grafana/Loki/Prometheus + docker compose --profile services --profile bot --profile obs up -d --build --remove-orphans + +services-observer: ## Start api + workers + bot + OpenObserve collector stack + docker compose --profile services --profile bot --profile observer up -d --build --remove-orphans + +services-nginx: ## Start full stack + nginx reverse proxy + docker compose --profile services --profile bot --profile edge up -d --build --remove-orphans # ───────────────────────────────────────────────────────────────────────────── # Docker: observability (Grafana + Loki logs) @@ -146,12 +169,20 @@ api: ## Start/restart API service api-logs: ## Tail API logs docker compose logs -f api -bot: ## Start/restart Telegram bot (same host as the api stack) - docker compose --profile services up -d --build --remove-orphans bot +bot: bot-up ## Alias for bot-up + +bot-up: ## Start/restart Telegram bot (same host as the api stack) + docker compose --profile bot up -d --build --remove-orphans bot + +bot-down: ## Stop Telegram bot + docker compose --profile bot down bot-logs: ## Tail bot logs (same host as the api stack) docker compose logs -f bot +bot-ps: ## Show bot container status + docker compose --profile bot ps + # ───────────────────────────────────────────────────────────────────────────── # Docker: bot on a separate server (HTTP-only adapter to central API) # ───────────────────────────────────────────────────────────────────────────── @@ -261,13 +292,14 @@ shell-api: ## Open shell inside API container docker compose --profile services exec api /bin/sh shell-bot: ## Open shell inside bot container - docker compose --profile services exec bot /bin/sh + docker compose --profile bot exec bot /bin/sh # ───────────────────────────────────────────────────────────────────────────── # Cleanup # ───────────────────────────────────────────────────────────────────────────── clean: ## Remove containers, volumes, caches docker compose --profile services down -v + docker compose --profile bot down -v docker compose down -v rm -rf .pytest_cache .ruff_cache uv cache clean @@ -275,10 +307,10 @@ clean: ## Remove containers, volumes, caches # ───────────────────────────────────────────────────────────────────────────── # Full workflow shortcuts # ───────────────────────────────────────────────────────────────────────────── -dev: install infra-up migrate services-up ## Bootstrap full dev environment +dev: install infra-up migrate services-up bot-up ## Bootstrap full dev environment (with bot) dev-obs: install infra-up migrate services-obs ## Bootstrap full dev environment with observability -stop: services-down infra-down ## Stop everything +stop: services-down bot-down infra-down ## Stop everything -stop-obs: obs-down services-down infra-down ## Stop everything including observability +stop-obs: obs-down services-down bot-down infra-down ## Stop everything including observability diff --git a/README.md b/README.md index 98f3d9d..f1a2302 100644 --- a/README.md +++ b/README.md @@ -138,16 +138,18 @@ uv run python -m contract_check.bot # Telegram-бот ```bash cp .env.example .env # заполнить секреты (DB, Rabbit, MinIO, LLM, BOT_TOKEN, JWT_SECRET, ...) docker compose up -d # только инфра с healthchecks -docker compose --profile services up -d --build # + api, worker-extract, worker-prescreen, worker-analyze, worker-notify, worker-billing, bot +docker compose --profile services up -d --build # + api, worker-extract, worker-prescreen, worker-analyze, worker-notify, worker-billing (без бота) +docker compose --profile bot up -d --build # + bot (можно запускать отдельно или на другом хосте) ``` -Или через `make`: `make dev` (install + infra + migrate + services), `make help` — полный список +Или через `make`: `make dev` (install + infra + migrate + api + workers + bot), `make help` — полный список целей (lint, typecheck, test, seed-token, jwt-token, admin-promote, логи/шеллы сервисов и т.п.). -Профиль `services` собирает 6 образов из `srv//Dockerfile` и поднимает их -с `depends_on: condition: service_healthy`. Edge-прокси (Nginx + certbot) доступен -профилем `edge` (`deploy/nginx/`, `docs/DEPLOY.md §13`). Observability (Prometheus/Grafana/Tempo/OTel) -— за будущим профилем `obs`. +Профили: +- `services` — api + worker-ы (без бота). +- `bot` — Telegram-бот; можно поднять на этом же хосте или на отдельном сервере (`docs/DEPLOY.md` §14). +- `edge` — Nginx + certbot (`deploy/nginx/`, `docs/DEPLOY.md` §13). +- `obs` / `observer` — observability (Grafana/Loki/Prometheus или OpenObserve + OTel collector). Порты на хосте (смещены, чтобы не конфликтовать): Postgres `15432`, Redis `17379`, RabbitMQ AMQP `5672` / UI `15672`, MinIO `9000` / console `9001`, api `8000` / metrics `9100`, diff --git a/docker-compose.bot.yml b/docker-compose.bot.yml index 8287459..dd7135f 100644 --- a/docker-compose.bot.yml +++ b/docker-compose.bot.yml @@ -27,11 +27,13 @@ services: BOT_TOKEN: ${BOT_TOKEN} API_URL: ${API_URL} BOT_SERVICE_TOKEN: ${BOT_SERVICE_TOKEN} - # Optional Redis for shared rate-limit state across bot replicas. - # Leave empty (or omit) to use the in-memory backend; safe for a single - # bot instance. If set, Redis must be reachable from this host (private - # network / VPN — do NOT expose Redis to the public internet). - REDIS_URL: ${REDIS_URL:-} + # Local Redis for rate-limit state. Set to empty to use the in-memory + # backend (fine for a single bot instance). The Redis container below is + # not exposed outside the host; only the bot container can reach it. + REDIS_URL: ${REDIS_URL:-redis://redis:6379/0} + depends_on: + redis: + condition: service_healthy healthcheck: test: - CMD-SHELL @@ -40,3 +42,19 @@ services: timeout: 5s retries: 3 start_period: 15s + + redis: + image: redis:8-alpine + container_name: contract_check-redis + restart: unless-stopped + command: ["redis-server", "--appendonly", "yes"] + volumes: + - redisdata:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 10 + +volumes: + redisdata: diff --git a/docker-compose.yml b/docker-compose.yml index aa2e5f9..54f967b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,11 @@ # «Контракт-чек» — infrastructure (Step 1). # # Default (`docker compose up`) starts ONLY infra: postgres + redis + rabbitmq -# + minio (+ minio-init). Service containers (api, worker-extract, worker-analyze, -# bot) and the observability/edge stacks are added behind profiles in later steps -# (docs/ARCHITECTURE.md §20). +# + minio (+ minio-init). Service containers are added behind profiles: +# `services` -> api + workers (no bot) +# `bot` -> Telegram bot adapter (can run on a separate host) +# `obs` / `observer` / `edge` -> observability and reverse proxy +# (docs/ARCHITECTURE.md §20, docs/DEPLOY.md §14). # # Durability posture (§10): quorum-ready. Postgres is configured # wal_level=replica + WAL archiving (replica/PITR-ready). RabbitMQ quorum queues @@ -295,19 +297,19 @@ services: - "9103:9103" # Telegram bot adapter (aiogram 3, HTTP-only to api). Per docs/ARCHITECTURE.md §17 - # the bot holds no DB/MQ/S3 credentials — it depends on `api` being healthy, - # not on the infra containers directly, enforcing the hexagonal boundary even - # in dependency ordering. + # the bot holds no DB/MQ/S3 credentials — it talks only to the API. + # It is intentionally isolated in profile `bot` so api + workers can start + # without it; the bot can also run on a different host. See docs/DEPLOY.md §14. bot: - profiles: ["services"] + profiles: ["bot"] build: context: . dockerfile: srv/bot/Dockerfile container_name: contract_check-bot restart: unless-stopped - depends_on: - api: - condition: service_healthy + # Note: no depends_on api. The bot healthchecks the API at runtime and + # restarts via `restart: unless-stopped` if the API is not yet ready. + # This keeps the bot profile self-contained and deployable on a separate host. environment: ENV: ${ENV:-dev} LOG_LEVEL: ${LOG_LEVEL:-INFO} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9d68629..845cdb6 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1277,10 +1277,13 @@ dev = [{ include-group = "api" }, { include-group = "extract" }, ### docker-compose.yml structure -One file, profiles. Default (`docker compose up`) = infra only. Services -behind `--profile services`. Two observability stacks are mutually exclusive: -`--profile obs` (Grafana + Loki + Prometheus) or `--profile observer` -(OpenObserve + OTel Collector). Edge behind `--profile edge`. +One file, profiles. Default (`docker compose up`) = infra only. +- `services` profile = api + workers (no bot). +- `bot` profile = Telegram bot adapter; can run on the same host or a separate + server (`docs/DEPLOY.md` §14). +- `obs` (Grafana + Loki + Prometheus) and `observer` (OpenObserve + OTel + Collector) are mutually exclusive observability stacks. +- `edge` = Nginx + certbot. ``` services: @@ -1299,7 +1302,10 @@ services: worker-analyze: build srv/worker-analyze/Dockerfile; depends_on pg/rabbit/minio-init healthy; 9102 worker-billing: build srv/worker-billing/Dockerfile; depends_on pg/minio-init healthy; 9105 worker-notify: build srv/worker-notify/Dockerfile; depends_on rabbit healthy; 9103 - bot: build srv/bot/Dockerfile; depends_on api healthy (NOT pg/rabbit) + + # ── BOT (profile: bot) ── + bot: build srv/bot/Dockerfile; no depends_on so it can run on a + separate host; healthchecks API at runtime # ── OBSERVABILITY — GRAFANA STACK (profile: obs) ── loki: grafana/loki; filesystem-backed single-node log store diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 2d6e0c5..f21c14a 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -80,12 +80,20 @@ uv run alembic upgrade head docker compose --profile services run --rm api alembic upgrade head ``` -### 2.4 Полный стек (api + workers + bot) +### 2.4 Сервисы (api + workers) и бот ```bash +# api + workers (без бота): docker compose --profile services up -d --build + +# Telegram-бот отдельно (можно на этом же или на другом хосте): +docker compose --profile bot up -d --build + +# Или сразу весь стек: +docker compose --profile services --profile bot up -d --build + # Ждём healthy: -docker compose --profile services ps +docker compose --profile services --profile bot ps ``` --- @@ -190,7 +198,8 @@ make shell-db # UPDATE plans SET price_kopecks = 59000 WHERE code = 'lite'; | Профиль | Что поднимает | |---|---| | *(default)* | `postgres`, `redis`, `rabbitmq`, `minio`, `minio-init` | -| `services` | `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-billing`, `worker-notify`, `bot` | +| `services` | `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-billing`, `worker-notify` | +| `bot` | `bot` — Telegram-адаптер; можно поднять на этом же хосте или на отдельном сервере | | `obs` | `loki`, `promtail`, `grafana`, `prometheus` (logs + metrics; traces — позже) | | `observer` | `openobserve`, `otel-collector` (logs + metrics + traces через collector) | | `edge` | `nginx`, `certbot` | @@ -199,24 +208,32 @@ make shell-db # UPDATE plans SET price_kopecks = 59000 WHERE code = 'lite'; # Инфра: docker compose up -d -# + сервисы (сборка + запуск): +# api + workers (без бота): docker compose --profile services up -d --build +# Только бот (например, на отдельном сервере или после api + workers): +docker compose --profile bot up -d --build + +# Полный стек на одном хосте: +docker compose --profile services --profile bot up -d --build + # + observability Grafana/Loki/Prometheus (логи + метрики): -docker compose --profile services --profile obs up -d --build +docker compose --profile services --profile bot --profile obs up -d --build # + observability OpenObserve (логи + метрики + трейсы через otel-collector): -docker compose --profile services --profile observer up -d --build +docker compose --profile services --profile bot --profile observer up -d --build # + edge (nginx reverse proxy + TLS; deploy/nginx готов): -docker compose --profile services --profile observer --profile edge up -d --build +docker compose --profile services --profile bot --profile observer --profile edge up -d --build ``` ### 4.2 depends_on и healthchecks `api` ждёт `postgres`, `rabbitmq`, `minio-init` (healthy / completed). -`bot` ждёт только `api` (healthy) — не инфра напрямую, соблюдая hexagonal boundary. Workers ждут `postgres`, `rabbitmq`, `minio-init`. +`bot` не имеет `depends_on` — он находится в отдельном профиле `bot` и может +запускаться на другом хосте. На старте бот проверяет доступность API через +встроенный healthcheck и перезапускается при необходимости (`restart: unless-stopped`). --- @@ -274,9 +291,9 @@ asyncio.run(seed()) ### 5.3 Перезапуск бота с новым токеном ```bash -docker compose --profile services restart bot +docker compose --profile bot restart bot # Проверка логов: -docker compose --profile services logs -f bot +docker compose --profile bot logs -f bot ``` --- @@ -348,8 +365,8 @@ docker compose exec postgres psql -U contract_check -d contract_check \ ### 7.2 Запуск ```bash -docker compose --profile services up -d bot -docker compose --profile services logs -f bot +docker compose --profile bot up -d --build +docker compose --profile bot logs -f bot ``` Ожидаемый вывод при `/start`: @@ -437,10 +454,13 @@ MinIO хранит raw-документы и extracted `.txt`. ILM-правил # 1. Pull изменений git pull origin main -# 2. Rebuild + recreate (Compose пересоздаёт только изменённые контейнеры) +# 2. Rebuild + recreate api + workers (Compose пересоздаёт только изменённые контейнеры) docker compose --profile services up -d --build -# 3. Миграции (если есть новые) +# 3. Пересоздать бота (если он запущен на этом хосте) +docker compose --profile bot up -d --build + +# 4. Миграции (если есть новые) docker compose --profile services run --rm api alembic upgrade head # 4. Проверка: @@ -543,8 +563,11 @@ docker compose exec postgres psql -U contract_check -d contract_check \ ### 12.6 Бот не отвечает ```bash -# Проверка логов -docker compose --profile services logs -f bot +# Проверка логов (если бот на этом хосте) +docker compose --profile bot logs -f bot + +# Если бот на отдельном сервере — смотрите логи там: +# docker compose -f docker-compose.bot.yml logs -f # Проверка polling: # Бот использует polling по умолчанию (aiogram). Если webhook установлен — @@ -563,14 +586,17 @@ NGINX_SERVER_NAME=contract-check.example.com ``` > **Важно:** nginx зависит от `api` (`service_healthy`), а `api` находится в -> профиле `services` — поэтому nginx/certbot всегда запускайте с обоими -> профилями: `docker compose --profile services --profile edge ...`. +> профиле `services` — поэтому nginx/certbot всегда запускайте хотя бы с +> профилями `services` и `edge`. Бот (`profile: bot`) можно добавить по желанию. > Запуск только `--profile edge` падает с `service "nginx" depends on > undefined service "api"`. ```bash -# Запуск edge вместе со стеком (после получения первого сертификата, см. §13.2) +# Запуск edge вместе со стеком api + workers (без бота): docker compose --profile services --profile edge up -d + +# Запуск edge вместе с полным стеком (api + workers + бот на этом хосте): +docker compose --profile services --profile bot --profile edge up -d ``` ### 13.2 Первый запуск и получение сертификата @@ -585,7 +611,8 @@ Nginx **не может стартовать без сертификата** — nginx стартует. Всё это делает `certbot-init.sh`: ```bash -# 1. Запускаем основной стек (nginx пока НЕ стартуем) +# 1. Запускаем основной стек (nginx пока НЕ стартуем). +# Добавьте --profile bot, если бот должен работать на этом же хосте. docker compose --profile services up -d # 2. Получаем первый сертификат (standalone, порт 80) и стартуем nginx + renew-sidecar @@ -608,8 +635,8 @@ curl https://contract-check.example.com/healthz Grafana под путём `/grafana`: ```bash -# Открыть логи через основной домен (нужен профиль obs + edge) -docker compose --profile services --profile obs --profile edge up -d +# Открыть логи через основной домен (нужны профили services/bot/obs/edge) +docker compose --profile services --profile bot --profile obs --profile edge up -d # https://contract-check.example.com/grafana/d/contract-check-logs ``` @@ -651,6 +678,7 @@ docker compose --profile services run --rm api alembic stamp head ```bash # Удалить ВСЕ данные (тома + контейнеры): docker compose --profile services down -v +docker compose --profile bot down -v docker compose down -v # Затем пересоздать с нуля: §2 + §5 ``` @@ -705,9 +733,13 @@ docker compose --profile services exec api python -m src.contract_check.api seed BOT_TOKEN=123456789:ABCDEF... # тот же токен из @BotFather BOT_SERVICE_TOKEN=bot-prod-secret-xxx # должен совпадать с service_tokens.name='bot-prod' API_URL=https://contract-check.example.com # публичный адрес центрального API, без trailing slash - REDIS_URL= # пусто → in-memory rate limiter; достаточно для одного инстанса + REDIS_URL=redis://redis:6379/0 # локальный Redis из docker-compose.bot.yml ``` + `docker-compose.bot.yml` поднимает собственный Redis-контейнер (только для + rate-limit, не для очереди). Чтобы использовать in-memory бэкенд, задайте + `REDIS_URL=` (пустое значение). + 3. Запустите: ```bash @@ -767,7 +799,7 @@ make bot-remote-ps - [ ] `.env` заполнен (включая `TELEGRAM_BOT_TOKEN`, `JWT_SECRET`) - [ ] `BOT_SERVICE_TOKEN` засеян в `service_tokens` - [ ] Миграции накатаны (`alembic upgrade head`) -- [ ] `docker compose --profile services ps` показывает все healthy +- [ ] `docker compose --profile services --profile bot ps` показывает все healthy (или `services` — если бот не на этом хосте) - [ ] `/healthz` и `/readyz` отвечают 200 - [ ] Telegram-бот отвечает на `/start` - [ ] Тестовый PDF проходит pipeline: upload → extract → analyze → report diff --git a/docs/TICKETS.md b/docs/TICKETS.md index cb148e3..162dbc9 100644 --- a/docs/TICKETS.md +++ b/docs/TICKETS.md @@ -29,7 +29,7 @@ | `worker_notify/` | done | `consumer.py`/`handler.py`/`__main__.py`: consume `NotificationMessage` → SMTP (password reset, magic link) или dev-лог при пустом `SMTP_HOST`. | | `bot/` — Telegram adapter | done | `client.py`/`config.py`/`handlers.py`/`__main__.py`: `/start`, upload→`POST /documents`, poll→deliver; граница импортов проверяется `tests/unit/test_bot_boundary.py`. | | Dockerfile-ы | done | `srv/{api,worker-extract,worker-prescreen,worker-analyze,worker-notify,bot,prototype}/Dockerfile` — все 7 (deps-группы PEP 735 заточены на сервис). | -| Docker Compose | done | Инфра (default) + профиль `services` (api, 3 worker-а, bot) + профиль `edge` (nginx+certbot) с `depends_on: service_healthy`. Профиль `obs` — позже. | +| Docker Compose | done | Инфра (default) + профиль `services` (api + worker-ы) + профиль `bot` (Telegram-адаптер, можно на отдельном хосте) + профиль `edge` (nginx+certbot) с `depends_on: service_healthy`. Профили `obs`/`observer` — позже. | | Observability | in_progress | Prometheus-метрики (`/metrics`), Sentry, OpenTelemetry SDK — в коде. Полный стек Prom/Grafana/Tempo/OTel-collector — позже. | | Stage 3 — B2B API | done | `api/routes/b2b.py`, `core/api_keys.py`, `core/rate_limit.py`, `core/redis_client.py`, миграция `0002_api_keys.py`, `tests/integration/test_b2b_api.py`, `tests/unit/test_rate_limit.py`; `X-API-Key` auth + token-bucket rate-limit. | @@ -73,7 +73,7 @@ ## Этап 1 — Production refactor (api + core + workers + bot) > Архитектура: hexagonal, RabbitMQ pipeline (`extract.q` → `analyze.q`), MinIO, Postgres, Redis. -> Профили Docker Compose: default = инфра; `services` = api + workers + bot. +> Профили Docker Compose: default = инфра; `services` = api + workers; `bot` = Telegram-адаптер. ### T-E1-001 — Ядро `contract_check.core` **Статус:** done · **Оценка:** L @@ -120,8 +120,9 @@ consume `DocumentExtracted` из `analyze.q` → скачать `.txt` → chunk **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-004, T-E1-005, T-E1-006 `docker-compose.yml`: default-профиль = инфра (`postgres`, `redis`, `rabbitmq`, `minio`, `minio-init`); -профиль `services` = `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-notify`, `bot` с `depends_on: service_healthy`; -профиль `edge` = nginx+certbot. Все 7 Dockerfile-ов в `srv/`. Профиль `obs` добавляет `loki` + `promtail` + `grafana` для логов (сделано в T-E1-010a). +профиль `services` = `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-notify` с `depends_on: service_healthy`; +профиль `bot` = `bot` (Telegram-адаптер), можно поднять на этом же или на отдельном хосте; +профиль `edge` = nginx+certbot. Все 7 Dockerfile-ов в `srv/`. Профили `obs`/`observer` добавляют observability-стек. ### T-E1-008 — Оплата (ЮKassa) и пополнение кредитов **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003, T-E1-006