# ✨ Отправлять готовый текст через apprise и ntfy

- **Тип:** feature
- **Категория:** Очередь — Канал уведомлений выбирается в настройках, которые уже есть.
- **Зачем:** Пользователь веба узнаёт о готовности только опросом с открытого экрана.

Готовый текст и отказ доходят до пользователя веба без открытого приложения,
отказ канала задачу не роняет, а пользователь Telegram получает ответ
по-прежнему ботом. Выбор канала самим пользователем заводит `settings-screen`.

Сегодня `completeJob` и `failJob` отвечают только источнику `telegram`;
источник `api` не получает ничего. Здесь появляется второй способ доставки, и
выбор между ними обязан остаться в одной точке — той же, что сейчас.

## Затрагивает

- `internal/contract` — интерфейс отправителя уведомления;
- `internal/service`, `completeJob` и `failJob` — выбор канала по источнику
  задачи;
- новый адаптер поверх apprise либо прямого HTTP к ntfy;
- адрес канала у учётной записи: колонка и её миграция (экран настройки заводит
  `settings-screen`);
- секция конфигурации: адрес сервера ntfy, способ вызова apprise;
- `docs/architecture.md` — новая внешняя зависимость и чем она отказывает;
- `docs/security.md` — текст расшифровки уходит на внешний сервис;
- `docs/database.md` — настройки числами.

## Критерии приёмки

- Задача из веба, дошедшая до `done`, отправляет текст в канал владельца.
  Оракул — тест с подставным отправителем: вызов ровно один, с текстом задачи и
  адресом владельца.
- Задача из Telegram по-прежнему отвечает ботом и вторым каналом не дублируется.
  Оракул — тот же тест на источнике `telegram`: подставной отправитель ntfy не
  вызван.
- Недоступный канал уведомлений не мешает задаче завершиться: состояние `done` и
  текст в приложении остаются. Оракул — тест с отправителем, возвращающим
  ошибку: задача в `done`, текст на месте, в логе одна запись уровня `WARN`.
- Отказ задачи доходит тем же каналом и тем же человекочитаемым текстом, что
  видит пользователь Telegram. Оракул — тест на ветке `failJob`.

## Рамки

Web Push с VAPID и своим хранением подписок не делаем. Своего сервера ntfy не
поднимаем — адрес приходит конфигом. Текст расшифровки уходит на внешний сервис,
и это сдвиг периметра: строка в `docs/security.md` обязательна.

Отказ от Web Push сегодня живёт открытым вопросом `docs/architecture.md`,
«Уведомления», и своего ADR не имеет: заводить его не из чего, пока нет
`design.md` этой задачи. Решение промоутится из него, когда задача пойдёт в
работу.
