Skip to content

bug: признак живости аккаунта (get_account active_only) не различает recoverable-inactive от permanently-dead → ложная деактивация канала #1279

Description

@axisrow

Выявлено Codex при dual-review PR #1270 (#1245 виток 3). НЕ регрессия #1245 — вскрыто новым фолбэк-путём, но корень в предсуществующей семантике get_account/load_live_usable_accounts.

Контекст

PR #1270 (#1245) добавил liveness-различение: при фолбэк-фейле на приватном single-owner канале skip'ать (сохранить preferred, не деактивировать) пока владелец ЖИВ — критерий _lease_pool.get_account(own, active_only=True) is not None; если None (мёртв) → рекордискавери + деактивация.

Проблема: get_account(active_only=True) — слишком грубый признак «жив/мёртв». Он зовёт load_live_usable_accounts (src/database/live_accounts.py), которая возвращает None не только для реально удалённого аккаунта, но и для восстановимых состояний. Два таких случая:

MAJOR #2 — decrypt-fail аккаунт классифицируется как мёртвый

get_live_usable_accounts пропускает аккаунты, где сессия не расшифровалась (AccountSessionDecryptError — битый/отсутствующий SESSION_ENCRYPTION_KEY). Владелец A с временно нечитаемой сессией + здоровый фолбэк B (не член) → get_account(A) = None → канал деактивируется. Восстановление ключа/ре-логин (src/web/templates/settings.html:20-27) НЕ реактивирует канал.
Файлы: src/telegram/account_lease_pool.py:130-135, src/database/live_accounts.py:10-23, src/database/repositories/accounts.py:437-473, src/telegram/collector_resolve.py:238-305.

MAJOR #3 — обратимая admin-пауза деактивирует sole-owner приватный канал

UI-тумблер pause/play ставит is_active=0 (задуман обратимым). Пока на паузе active_only=True фильтрует владельца → фолбэк-не-член → рекордискавери → set_channel_active(False). Снятие паузы возвращает клиент, но канал остаётся is_active=0 (bulk-сбор навсегда исключает неактивные — collector_mixins/collection.py:305-318). Обычное поведение админа, не гонка.
Файлы: src/web/templates/settings/_accounts.html:84-88, src/services/dispatcher/accounts_mixin.py:52-84, src/database/repositories/accounts.py:519-552.

MINOR — узкая гонка re-enable

collector_resolve.py:238-305 — состояние читается один раз, потом await-cleanup; re-enable между чтением None и видимостью рекордискавери всё равно деактивирует. Low-probability.

Что сделать (дизайн)

Различить permanently-dead (строка аккаунта удалена) от recoverable-inactive (decrypt-fail, admin-пауза, temporarily-disabled). Skip'ать (не деактивировать) для ВСЕХ recoverable-состояний, деактивировать только для реального удаления. Варианты: отдельный предикат is_account_permanently_gone(phone) (проверка существования row, не usability); либо не деактивировать канал по фолбэк-миссу вообще, а только помечать «нужен рекордискавери при следующем живом проходе».

Приоритет

priority/medium. Класс: ложная деактивация живого канала при восстановимом состоянии владельца.

Родитель: #1234. Связано: #1245 (закрыт в #1270 для основного бага + BLOCKER-атомарности).

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority/mediumСредний приоритет

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions