Files
jellybit/docs/adr/ADR-2026-08-10-observability-is-not-terminality.md
T
av a5d873b62d web-ui: карточка и страница обновляются, пока задачу может двигать фон
- условие самообновления — доменный предикат store.State.IsObservable() вместо
  фазы catched; один поллер на поверхность, интервалы 5 с и 15 с
- отказ тика отвечает 200 и самозавершающимся фрагментом с корневым id цели
  вместо 404/500, который htmx не свопит
- заведён ADR-2026-08-10-observability-is-not-terminality, переписан раздел
  «Живой поллинг» в конвенции веб-UI
2026-08-10 14:02:38 +03:00

4.5 KiB
Raw Blame History

Наблюдаемость поверхности не выводится из терминальности задачи

  • Дата: 2026-08-10
  • Источник: openspec/changes/archive/2026-08-10-card-live-refresh/design.md

Решение

Веб-UI обновляет себя, пока задача наблюдаема — то есть её состояние ещё может измениться без участия человека, — а не пока она нетерминальна. Предикат store.State.IsObservable() живёт в домене рядом с IsTerminal() и даёт: все нетерминальные плюс failed, target_missing, orphaned. Замолкают done, cancelled, reverted, deleted.

Почему

Очевидный предикат — «обновляемся, пока задача не терминальна» — оказался неверным, и это выяснилось на ревью дизайна, до кода. Цитата из источника:

Терминальность в проекте значит «не активна», а не «навсегда»: фоновая сверка двигает часть терминальных сама — ListRecoverable возвращает в поток failed/stuck с кодами magnet_timeout и stalled, а desyncStates переоценивает done, target_missing и orphaned. Карточка, застывшая по IsTerminal, показывала бы «Ошибка» у задачи, которая уже качается, — ровно тот дефект, ради которого затеян change.

done в перечень наблюдаемых не вошёл, и это отдельное решение с ценой:

Переход done → target_missing/orphaned означает, что файлы удалили руками мимо сервиса, — событие редкое, а карточек done в списке больше всех. Платить за редкий случай постоянным фоновым запросом на каждую разложенную задачу дороже, чем показать её новое состояние при следующем заходе.

Рассмотренные варианты

  • Наблюдать только нетерминальные (как задумывалось изначально) — проще всего и не заводит второго предиката. Отвергнут: задача, оживлённая сверкой из failed, висела бы на экране с надписью «Ошибка» до перезагрузки, причём соседние карточки при этом обновлялись бы — застывшая читалась бы как достоверная.
  • Наблюдать всё, терминальные — редким тиком — снимает вопрос целиком. Отвергнут: список из сотни разложенных задач слал бы пустые запросы вечно, а критерий приёмки «завершённая карточка себя не опрашивает» пришлось бы отменить.

Последствия

  • + смена состояния становится видимой независимо от того, кто её сделал: воркер, веб-UI, Telegram или фоновая сверка.
  • + условие обновления выражено одним доменным предикатом; второго перечня состояний в транспорте нет, и завести его нельзя не заметив.
  • в домене стало два перечня состояний вместо одного, и второй выведен из поведения воркера (desyncStates, ListRecoverable) вручную. Расширение сверки новым состоянием молча вернёт застывшую карточку — связки, которая бы это ловила, нет.
  • карточка failed, target_missing или orphaned опрашивает сервер, пока открыта вкладка: эти состояния живут долго и копятся (срока хранения нет).