Session close 2026-09-15 (вечер): imap_stream Фаза 1 реализован, backlog urgent закрыт (фильтр актуальности), live-тесты OK

This commit is contained in:
2026-09-15 19:10:08 +00:00
parent 5a81c86599
commit 99e8e847f3
3 changed files with 105 additions and 2 deletions
+77
View File
@@ -2,6 +2,83 @@
Воспроизводимость: хронология, команды, решения, ошибки и как чинили.
## 2026-09-15 (вечер, сессия @session:default/20260915_174925_ec1ca4)
### imap_stream.py — Фаза 1 (IDLE-цикл) реализован
**Цель:** постоянный IMAP-поток (задачи 3–5 change imap-realtime-sync). aioimaplib
не ставится → свой клиент на сыром socket поверх `imap_client.imap_connect`.
**Архитектура `scripts/imap_stream.py`:**
- `ImapStream` — обёртка над сокетом: теги, `_cmd`, `_cmd_ok`, чтение до тега.
- IDLE-цикл: `idle_start` → `idle_wait(25с)` → `idle_done` → перевыпуск.
- Reconnect: при ошибке `CONNECT_PAUSE=45с` (rate-limit Exchange), повтор.
- `reconcile_new(folder)` — поиск UID > last_uid, архивация через mail_archive,
запись в `mailbox_state` + событие `mailbox_events`.
- `reconcile_full(folder)` — полное сравнение UID/флагов (каждый 5-й цикл).
- SQLite: `/opt/hermes/email/state/mailbox.db`, таблицы `mailbox_state` (uid,
folder, message_id, in_reply_to, refs, flags, has_attachment, archive_path,
last_seen, deleted) и `mailbox_events` (event/ts/folder/uid/detail).
- CLI: `--check` (авторизация), `--test-idle` (IDLE 30с), `--status`, `--metrics`,
без аргументов — демон.
**Питфолы Exchange, найденные live-тестами (пауза ≥45с между коннектами):**
1. `_load_credentials` в imap_client.py читал `Path.home()` — в сессии Hermes
HOME≠реальный. Фикс: `HERMES_REAL_HOME` (env) → `/home/estorozhenko`. Cron
уже делает это в mail-archive.sh.
2. `_cmd_ok`: `resp.split(b"\r\n")[-1]` давал пустой элемент (хвост `\r\n`) —
команда считалась failed даже при OK. Фикс: разбор строк до тега.
3. Колонка `references` — зарезервированное слово SQLite → `refs`.
4. **UID-батчинг сломан:** `UID SEARCH UID 1:1000` пуст (UID — глобальный номер
~14200+, не порядковый); первый пустой батч обрывал цикл, все письма ложно
помечались deleted. Фикс: `UID SEARCH ALL` / `UID SEARCH UID <min>:*`
(проверено: 365 UID).
5. Exchange отвечает на IDLE `+ IDLE accepted, awaiting DONE command.` —
НЕ `+ idling`. Фикс: матч `+ IDLE` или `+ idling`.
6. **Exchange рвёт IDLE-соединение ~60с** (не 30 мин) — `IDLE_TIMEOUT=25с`
(перевыпуск до серверного лимита). Демон переживает обрыв: reconnect 45с.
**Live-тесты (все с паузой ≥45с):**
- `--check`: connect+LOGIN+SELECT 365 писем → OK.
- `--test-idle`: SELECT → reconcile (0 новых, deleted=0, flags=18) → IDLE 30с →
0 событий → DONE → EXIT=0.
- Демон 150с: stream_started → SELECT 365 → обрыв IDLE (~60с) →
reconnect_pause 45с → SELECT 365. Цикл переподключения работает.
**Git:** 5a81c86 запушен в gitverse (defcf53..5a81c86). Файлы: scripts/imap_stream.py
(новый, ~690 строк), scripts/email_handlers.py (+фильтр актуальности), scripts/imap_client.py
(+HERMES_REAL_HOME).
### Backlog urgent-pисем: корень найден + фильтр актуальности
**Проблема:** 10 urgent-писем не уходили в Telegram. Причина: при наличии
python-dotenv `email_handlers.py` шёл в ветку `if load_dotenv` — грузил только
`BASE_DIR/.env` и `radicale/.env`, НО НЕ `/opt/vesti/.env` → `VESTI_BOT_TOKEN`
пуст → все обработки падали. Строка `✗ [urgent] ...: Нет токена Telegram`.
**Фикс (email_handlers.py):** `/opt/vesti/.env` грузится всегда (до ветвления).
**Запрос пользователя (mid-turn):** «перестань слать неактуальные срочные
уведомления; встроить проверку на актуальность — сравнивать дату письма и
текущую». Реализовано:
- `URGENT_MAX_AGE_DAYS = 3` — письма старше 3 дней не шлются.
- `is_urgent_recent(headers)` — парсит дату из frontmatter, сравнивает с now.
- Старое письмо: `✓ [urgent] ...: пропущено (актуальность истекла)`, помечается
`handled_urgent` (идемпотентность, не перебирается).
**Результат backlog:** 5 сентябрьских доставлены в ЛС (msg_id 62–64 и далее),
5 старых (июль/авг) — пропущены фильтром корректно. Январьское «Сервер» — на
деле `classification: info` (не urgent, ошибка подсчёта). 14229 помечен вручную
(уже дважды уведомлён — предотвращён дубль).
**Известный отдельный баг (не urgent):** `✗ [task] ...: PUT 400: Bad Request`
(Radicale VTODO-задача) — не разобран, открыт на следующую сессию.
### Cron-заметка
`mail-classify-handlers` (в venv /opt/vesti/.venv/bin/python, --limit 2/прогон)
работает; после фикса токена обрабатывает по 2 письма за прогон идемпотентно.
## 2026-09-11
### Фаза 1.7: динамическое обнаружение подпапок INBOX