Правила работы — `AGENTS.md` в корне репозитория. Репозиторий: `https://github.com/ochenstarik-ui/kagent`, база — свежий `main` Ветка: новая от `origin/main`, `wt/c5-nats-events` **Зависимостей нет.** Задача трогает Python-сервисы и конфигурацию, с c1 и c4 по файлам не пересекается и может идти параллельно. ## Проблема `services/nats/src/events.py` — клиент JetStream: подключение, публикация доменного события, подписка durable-консьюмером, запрос-ответ. Модуль не импортируется ни одним сервисом и попал в `docs/known-drift.json`. Кроме того, он не заработал бы и при подключении: `subscribe()` вызывает `pull_subscribe` с именем потока, который нигде не создаётся. Вызова `add_stream` в модуле нет. Первая же подписка упадёт на отсутствующем потоке. Каталог `services/nats` при этом не является сервисом: в нём нет ни `requirements.txt`, ни `Dockerfile`, ни записи в `docker-compose.yml`. Это библиотека, лежащая в каталоге сервисов. ## Что сделать ### 1. Починить клиент - создавать поток идемпотентно перед публикацией и подпиской: если поток отсутствует — создать с предметами по префиксу, если существует — использовать существующий; - имя потока и набор предметов вычислять по одному правилу, а не двумя разными местами в коде; - подтверждение и отрицательное подтверждение сообщения уже есть — сохранить; - при недоступности брокера соединение повторяется с ограничением, а не бесконечно. ### 2. Переместить как библиотеку `services/nats` — не сервис. Перенести модуль туда, где он является общей библиотекой Python-сервисов, например `packages/py-events` либо `services/_shared`, и подключить как зависимость к использующим сервисам. Выбранный вариант обосновать одной строкой в отчёте: структура каталогов описана в ТЗ, раздел 7, и не должна расходиться с фактом. ### 3. Подключить к одному сервису Достаточно одного настоящего потребителя, чтобы возможность считалась подключённой. Выбрать `services/pipeline`: он уже выполняет шаги и знает об их результате. Публиковать доменные события жизненного цикла шага с именами из ТЗ, раздел 10: `task.started`, `agent.started`, `agent.completed`, `artifact.created`, `task.failed`. Событие содержит идентификатор, тип, версию схемы, время, идентификаторы проекта и задачи, корреляционный идентификатор — как требует ADR-0002. - адрес брокера из `NATS_URL`, значение по умолчанию и запись в `.env.example`; - `nats-py` добавить в `requirements.txt` сервиса; версия уже используется в `services/orchestrator`, взять ту же; - в `docker-compose.yml` добавить зависимость `pipeline` от `nats` по состоянию работоспособности; - **недоступность брокера не должна ронять пайплайн.** Публикация — побочный эффект, а не условие выполнения шага. При недоступности брокера событие теряется с записью в лог, шаг продолжается. Это осознанный выбор на текущем этапе: гарантированная доставка требует outbox из ADR-0010, которого ещё нет. Отметить это в отчёте. ### 4. Доказательство Модульные тесты на заглушке брокера: событие сериализуется с обязательными полями, имя потока вычисляется одинаково для публикации и подписки, недоступность брокера не прерывает работу. Интеграционный тест с настоящим брокером в непрерывной интеграции: сервисный контейнер `nats:2.11-alpine` с включённым JetStream, публикация и получение события подписчиком, повторное создание потока не ломает работу. Добавить возможность в `docs/capabilities.json` со ссылкой на имя нового job как на доказательство и снять запись из `docs/known-drift.json`. ## Чего не делать - публикация событий из Control Plane на TypeScript — отдельная задача, там понадобится своя реализация клиента; - outbox и гарантированная доставка — ADR-0010, отдельный этап; - подключение оркестратора: он вообще отсутствует в `docker-compose.yml`, это отдельная проблема. ## Критерий приёмки ```bash ruff check services scripts python -m pytest tests/unit -q python scripts/drift_check.py ``` - модуль импортируется настоящим сервисом, а не только тестом; - `drift_check.py` не сообщает про модуль событий; в `known-drift.json` осталось две записи либо одна, если c3 уже принята; - job с брокером в CI зелёный, интеграционный тест публикует и получает событие; - `roadmap_status.py` показывает возможность событийного потока подтверждённой доказательством — вывод до и после в отчёте; - ссылка на зелёный прогон приложена.