- условие самообновления — доменный предикат store.State.IsObservable() вместо фазы catched; один поллер на поверхность, интервалы 5 с и 15 с - отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели вместо 404/500, который htmx не свопит - заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел «Живой поллинг» в конвенции веб-UI
14 KiB
14 KiB
Ревью изменения card-live-refresh — сводный отчёт триажа
Сводка
- Размер / сложность / метка: среднее / знакомое /
medium. - Режим прогона: по графу. База диффа
969926f. - Гейт: зелёный.
BASE=969926f task gateпрогнан проходомautotestsнезависимо: 14 шаговOK, ни одногоSKIP/WARN;-raceреально исполнен, флаки-прогон побайтово совпал, diff-coverage 24/24,gitleaksпо 187 коммитам чисто,govulncheck— 0 достижимых. - Находок на входе: 15 (autotests 2, specs 3, code 7, basics 4) + 3 наблюдения «вне спеки»
- 3 «дешевле переделать до мерджа». На выходе: 2 блокирующие + 4 «сейчас» + 4 гипотезы
- 5 promote.
План разметки с исходом по темам
| Тема | Дом | Глубина | Кто закрывает | Исход |
|---|---|---|---|---|
| requirements | openspec/specs/{web-ui,live-status} + дельты |
разбор | specs |
закрыта, 3 находки |
| autotests | CLAUDE.md «Гейт» | прогон | autotests |
закрыта, гейт прогнан, 2 находки |
| conventions | docs/conventions/{README,web-ui,logging}.md |
разбор | code |
закрыта, 3 находки |
| architecture | docs/architecture.md + passport.md |
разбор | basics |
закрыта, 1 находка |
| security | docs/security.md |
разбор | basics |
закрыта, 0 находок, все 5 вопросов отвечены |
| operations | docs/architecture.md «Эксплуатация» + docs/database.md |
разбор | basics |
закрыта, 2 находки (обе понижены) |
| техника (без темы) | — | разбор | code |
4 находки, 3 дедуплицированы |
Тем без отчёта нет. Своих тем проекта план не называл.
Сигнал о заниженной метке
review-code — сигнала нет, возражений против medium не подаёт. review-basics строки о
метке не прислал; «возражений нет» и «не проверял» по молчанию не различаются — сигнал по
этому проходу считается неполученным, а не отрицательным.
Блокирует мердж
1. Тик страницы /download/{id} при отказе чтения оставляет её навсегда устаревшей и стучит в сервер до закрытия вкладки
- Файл:
internal/httpapi/download.go:84-102;web/templates/partials/download_main.html:2 - Severity: major. Confidence: high.
- Оракул: временный падающий тест триажа (прогнан, файл удалён):
page tick (not found) status = 404, want 200;page tick (store failure) status = 500; «ответ тика не несёт корня#download-main». Плюс дельтаweb-ui: требование написано для обеих поверхностей. - Последствие: htmx не свопит 4xx/5xx и не снимает
hx-trigger— страница показывает состояние, которого уже нет, человеку не сообщается ничего, вкладка стучит каждые 15 с бесконечно, добавляя строку ERROR за тик. До change это было незаметно:SelfPollстоял только на короткоживущемcatched, теперь наблюдаемых состояний 11. - Найдено четырьмя проходами независимо (
specs,basics,code,autotests). - Действие: развилка (общая со следующей находкой).
2. Фрагмент отказа с чужим корневым id сносит #review-main и #download-main — экран мёртв до F5
- Файл:
internal/httpapi/live.go:175-182;web/templates/partials/frag_note.html:1 - Severity: major. Confidence: high.
- Оракул: падающий тест триажа (тик
/reviewпри не-ErrNotFoundотказе отдал<article class="card" id="card-…">), плюсdocs/conventions/web-ui.md:36-41дословно: «корень{{define}}— это элемент с целевымid… если ответный фрагмент не несёт тот же корневойid, следующее действие/поллер не найдёт таргет». - Последствие:
fragErrзовут из шести мест с четырьмя разными целями свопа, а отдаёт он всегда карточку. ТранзиентныйSQLITE_BUSYна тикеrecognizing(самый частый тик проекта, 2 с) заменяет весь#review-mainкарточкой списка — экран ревью теряет якорь и все действия. Регрессия против базы: раньше 500 не свопился и экран оставался рабочим. - Действие: развилка (тот же вопрос, отвечать один раз на обе).
Стоит исправить сейчас
3. Тик страницы затирает то, что человек в этот момент читает
- Файл:
web/templates/partials/download_main.html:2,6,99-128;internal/httpapi/httpapi.go:515-526 - Severity: minor. Confidence: high.
- Два проявления одной причины: (1) сообщение об отказе действия живёт ≤15 с и исчезает, а в
error_msgштатный конфликт не пишется и в логе онDEBUG— причина не остаётся нигде; (2) раскрытая «Опасная зона» захлопывается каждый тик, пока человек читает текст про необратимое удаление раздачи с файлами. ПересечениеDismissableсIsObservable—failed,orphaned,target_missing. - Действие: развилка.
4. Отказ на повторяющемся тике пишется ERROR — шторм в журнале ровно тогда, когда хранилищу плохо
- Файл:
internal/httpapi/live.go:139-143,internal/httpapi/live.go:175-180 - Severity: minor. Confidence: high.
- Оракул:
docs/conventions/logging.md:164-171дословно: «Повторяющийся сбой фонового цикла (поллинг/сверка) —WARN, неERROR… уровень задаёт не текст ошибки, а наличие штатного ретрая». - Действие: инлайн.
5. Конвенция и комментарии описывают поллер, снятый этим же диффом
- Файл:
docs/conventions/web-ui.md:104-129;internal/httpapi/live.go:121-124;internal/httpapi/httpapi.go:314-318 - Severity: minor. Confidence: high.
- Раздел «Живой поллинг» утверждает три неверных вещи: пример разметки с
hx-*вprogress; «эталон —progress/seeding»; «данные тика — из in-memory снимка, без БД/сети на каждый тик». Следующий автор возьмёт за образец снятое и заведёт второй поллер на поверхность. - Действие: инлайн.
6. Тест TestFragProgressStopsWhenNotDownloading больше не может упасть
- Файл:
internal/httpapi/live_test.go:35-48 - Severity: minor. Confidence: high.
hx-*сняты с партиала безусловно, поэтому проверка ложна при любом состоянии: тест зелен независимо от логики, которую называет. diff-coverage меряет исполнение, а не проверку.- Действие: инлайн.
Гипотезы без доказательства
- Тик списка шлёт N HTTP-запросов и 2N запросов в SQLite там, где полный рендер обходится одним
батчем (
live.go:133-146). Понижено: замера нет; по-карточное обновление заказано дельтой, запрос идёт по индексуidx_file_link_download. Остаётся верным одно: арифметика Р4 вdesign.mdзанижена по числу обращений к БД. - Отказ чтения размеров подменяет известный размер прочерком (
live.go:136-144). Понижено: дельта про отказ вспомогательного чтения молчит — вопрос к тексту дельты, не дефект кода. - Перечень «кого фон возвращает сам» разошёлся на три ручные копии
(
store/download.go:67-84,worker/reconcile.go:22-26). Понижено: расхождения и последствия сегодня нет, риск чисто будущий. pollFast = "5s"— второй дом настройки[worker].poll_interval. Понижено: связь держится на прозе, сегодняшнее значение верно.
Отсеяно как вкусовщина: «имя IsObservable и комментарий расходятся с поведением на
review/deferred» — предикат ровно такой, каким его определила дельта-спека поимённо.
Проектных ложноположительных не сработало.
Promote candidates
- Правило «ответ-фрагмент несёт корневой
idтого узла, в который свопится» — записано в конвенции, но не механизировано; кандидат в табличный тест илиinternal/archrules. - Задача на уборку маршрутов-гасителей
/fragments/downloads/{id}/progressи/seeding. - Правило «константа, дублирующая значение настройки конфига, считается из конфига».
- Тест-связка
worker.desyncStates↔store.selfHealingStates. - Правило «тест, который не может упасть, — дефект теста»; класс ловится мутационной проверкой, diff-coverage его не видит по устройству.
Границы покрытия
- Запускались на метке
medium, режим «по графу»:autotests,specs,code,basics. Все четыре вернули отчёт. Триаж — сток. - Не запускались (нет на
medium): враждебный проход с построенным путём атаки, эксплуатационный постмортем с замерами, независимая реализация. Их даёт толькоlarge. - Потолки:
code/conventions3 из 4, срез не сработал;basics4 из 4 — срез сработал, за ним осталось наблюдение про ERROR на каждом тике (выведено отдельной находкой) и три пункта «дешевле переделать до мерджа».specs,autotests,code/техникапотолков не сообщили — это находка о самом прогоне. - На
smallиmediumничего не проверяется запуском сверх гейта: построенный путь атаки, поведение библиотеки и драйвера в вырожденном случае, любые числа (время удержания блокировки, пик кучи, темп роста журнала).basicsзадаёт часть тех же вопросов чтением — его ответы слабее и выше гипотезы не поднимаются. - Решения проекта не сверялись:
docs/adr/— процессный документ, прогон его не открывает; расхождение с записанным решением ловитav-dev-docs:healthcheck. - Записанные наблюдения не использовались:
docs/research/— тоже процессный; всякое число в отчёте снято на этом прогоне. - Поимённая сверка с руководствами по стилю Go не задавалась ни одним проходом — проход
idiomупразднён (ADR-2026-08-04). - Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет.
- Ни один тик в этом прогоне не исполнялся браузером: выводы о свопе и
hx-triggerсделаны из разметки и текста конвенции.