Выявлено 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-атомарности).
Выявлено 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-атомарности).