Deal — единая кодовая база
ci / build-test (push) Canceled after 0s

SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/
Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue,
контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер),
Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог).

Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0,
тесты 1340/130/52/38/9 зелёные.
This commit is contained in:
Rustam Khalimov
2026-09-11 23:56:47 +03:00
commit 27c7831910
1383 changed files with 158436 additions and 0 deletions
@@ -0,0 +1,84 @@
# Task 2 — Отчёт: BanGuard (квоты, паузы, flood)
Статус: **DONE**
## Что сделано по шагам
### Step 1: Модуль `backend/app/services/ban_guard.py` (создан)
Реализован по интерфейс-спеке брифа (в брифе «дословного» кода нет — только сигнатуры
и поведение, см. Concerns #1):
- `joins_today_auto() -> int``count(*)` из `disc_log` по `event='join_auto'`
с `created_at >= начало текущих UTC-суток` (`datetime.now(timezone.utc)
.replace(hour=0,minute=0,second=0,microsecond=0)` → ms).
- `can_auto_join() -> bool``joins_today_auto() < discJoinLimit` И `not flood_today()`
И `not global_paused()`.
- `async wait_join_delay() -> None` — `asyncio.sleep(random.uniform(discJoinDelayMin,
discJoinDelayMax))` (значения из `store.get_setting`).
- `note_flood() -> None` — `store.set_setting("discFloodDay", <start_of_day_ms>)`.
- `flood_today() -> bool` — `discFloodDay == start_of_day_ms` (вчерашний флуд-день
автоматически «протухает» в полночь UTC).
- `global_paused() -> bool` / `set_global_pause(v: bool) -> None` — setting `discPaused`.
- `search_pause() -> float` — `random.uniform(2.0, 4.0)`.
Детали реализации:
- Хелпер `_start_of_day_ms()` общий для квоты, флуда и паузы.
- Ключи настроек вынесены в модульные константы (`_KEY_*`) — в коде нет «голых» строк.
- `discPaused`/`discFloodDay` не в `DEFAULT_SETTINGS` (рантайм-настройки): `get_setting`
возвращает `None`, который трактуется как «не взведено» (`False`/`0`).
- Стиль модуля — как в соседних сервисах: `from ..db import store`, русские докстринги,
`from __future__ import annotations`.
### Step 2: Проверка на временной БД в контейнере
Первая попытка упала: `ImportError: cannot import name 'ban_guard'` — код копируется в
образ при сборке (`backend/Dockerfile`: `COPY backend/app ./app`), исходники не
монтируются, а образ был собран до создания файла. Выполнена пересборка
`docker compose build app` (2.6s, слой кода — единственный не из кэша), затем команда
из брифа прошла (вывод ниже).
## Изменённые файлы
- `backend/app/services/ban_guard.py` (создан)
## Вывод проверок
### 1. `py_compile`
```
$ cd /c/telbase && python -m py_compile backend/app/services/ban_guard.py
COMPILE_OK # без ошибок
```
### 2. Временная БД в контейнере (команда из брифа Step 2)
```
$ docker compose build app # необходимо, т.к. код запекается в образ
$ docker compose run --rm --no-deps -e PYTHONPATH=/srv -e LEADRADAR_DATA=/tmp/lr_bg --entrypoint python app -c "
from app.db import store; store.init()
from app.services import ban_guard as bg
assert bg.can_auto_join() is True
assert bg.joins_today_auto() == 0
bg.note_flood(); assert bg.flood_today() is True
bg.set_global_pause(True); assert bg.can_auto_join() is False
print('BANGUARD OK')
"
BANGUARD OK
```
Все 4 assert'а из брифа прошли. ✔
### Дополнительно
- Статическая проверка (diagnostics Zed): ошибок и предупреждений нет.
## Concerns
1. **«Весь код — в брифе» — фактически неверно**: в `task-2-brief.md` (и в плане)
нет ни одного блока кода модуля, только интерфейс-спека (~10 сигнатур с описанием
поведения) и команда проверки. «Транскрибировать дословно» было нечего; модуль
реализован по спеке. Очевидных ошибок/противоречий в спеке не нашёл — править было
нечего. Если планировался эталонный код — его нужно добавить в бриф.
2. `wait_join_delay()` и `search_pause()` не покрыты проверкой Step 2 (оба — случайные
паузы; дефолт паузы вступления 50–70 сек, поэтому в проверку они не входили).
Логика тривиальная (`random.uniform` + `asyncio.sleep`), но «живого» прогона нет.
3. `can_auto_join()` при `discJoinLimit <= 0` всегда `False` (осторожная сторона:
лимит 0 = «не вступать»).
4. Клампы пауз (5..600) независимы — `min > max` теоретически возможно через UI
(унаследованный concern из Task 1; в `wait_join_delay` тогда диапазон
«вывернется», но не упадёт). Перекрёстную проверку бриф не требовал.
5. Для проверок последующих задач нужна пересборка образа после каждого изменения
backend-кода (образ не монтирует исходники).