Часть 5 · ~11 мин
Сквозная фича: несколько сервисов и NuGet-пакет
Фича, которая не помещается в репозиторий
Задача: показывать покупателю трекинг доставки. Что затронуто:
orders-service— публикует событиеOrderTrackingUpdated;notifications-service— консьюмит его и шлёт уведомление;Syn.Contracts(NuGet) — тип события живёт здесь, его потребляют оба сервиса.
Три репозитория, три PR, один контракт. В варианте «спека в каждом репо» этот контракт пришлось бы описать трижды — и три копии разъехались бы к концу квартала. Здесь начинает окупаться store из части 3.
Ставим store — один раз на команду
Кто-то один (у «Синтеза» — Даша) создаёт репозиторий планирования:
openspec store setup syn-specs --path ~/openspec/syn-specs \
--remote git@github.com:sintez/syn-specs.git
git -C ~/openspec/syn-specs push -u origin main
--remote записывает URL клона в файл идентичности store — каждый будущий клон «знает», откуда он, и подсказки для коллег генерируются готовыми к копипасту.
Каждый инженер — один раз на машину:
git clone git@github.com:sintez/syn-specs.git ~/openspec/syn-specs
openspec store register ~/openspec/syn-specs
Важно понимать модель: store — это обычный git-репозиторий. OpenSpec никогда сам не клонирует, не пуллит и не пушит. Изменение спеки в store — это ветка, PR и ревью, ровно как код. Устаревший локальный клон показывает устаревшие спеки, пока вы не сделаете git pull — это не баг, это git.
Код-репо декларируют связь (мы выбрали гибрид, поэтому references, а не store:):
# orders-service/openspec/config.yaml — и так же в notifications-service, syn-libs
references:
- syn-specs
Двухслойная схема: контракт наверху, реализация внизу
Слой 1. Общий контракт — изменение в store
openspec new change add-delivery-tracking --store syn-specs
Спека этого изменения описывает поведение на границе компонентов — и только его:
### Requirement: OrderTrackingUpdated event
Событие ДОЛЖНО содержать OrderId, TrackingNumber, Carrier
и Status (Registered | InTransit | Delivered).
#### Scenario: Смена статуса перевозчиком
- GIVEN заказ с зарегистрированным трек-номером
- WHEN перевозчик сообщает новый статус
- THEN orders-service публикует OrderTrackingUpdated
- AND notifications-service отправляет покупателю уведомление
Этот change ревьюится PR-ом в репозитории syn-specs. Ревьюеры — владельцы обоих сервисов и Настя как владелица Syn.Contracts: контракт согласован до того, как написана первая строка кода.
Слой 2. Реализация — локальные изменения в каждом репо
После мержа контракта в основные спеки store каждый репозиторий заводит свой маленький change:
cd ~/src/syn-libs
qwen> /opsx-propose add-tracking-event-contract # тип события в Syn.Contracts
cd ~/src/orders-service
qwen> /opsx-propose implement-tracking-publisher
cd ~/src/notifications-service
qwen> /opsx-propose implement-tracking-consumer
Благодаря references агент в каждом репозитории видит индекс спек store — с готовой командой openspec show order-tracking --type spec --store syn-specs. Локальный proposal цитирует общий контракт, а его tasks.md описывает только работу этого компонента. Дальше в каждом репо — обычный цикл из части 4: apply, PR, archive. Три PR тестируются, ревьюятся и мержатся независимо.
Порядок раскатки диктует NuGet: сначала Syn.Contracts (новая версия пакета), затем publisher, затем consumer — или в любом порядке, если событие спроектировано обратно-совместимым. Это решение тоже место в спеке контракта, а не в чьей-то голове.
NuGet-библиотека: два вида требований — два адреса
Точка, где команды чаще всего путаются. Правило из части 3 в действии:
| Требование | Пример | Где спека |
|---|---|---|
| Публичный контракт пакета | «OrderTrackingUpdated обязан сериализоваться как raw JSON без envelope» | store syn-specs — его читают все потребители |
| Реализация пакета | «внутренний кеш резолвера типов не аллоцирует на горячем пути» | openspec/ в репозитории syn-libs — это дело Насти и её ревью |
Потребитель пакета никогда не должен ходить в чужой репозиторий, чтобы узнать, что ему обещано. Обещания — в store; как обещание выполнено — приватное дело библиотеки.
Гигиена процесса
openspec doctor— проверка здоровья: все ли referenced stores зарегистрированы на этой машине; каждая находка — с готовой командой-фиксом.openspec context— «с чем я сейчас работаю»: корень + referenced stores;--jsonдля агентов.- Worksets — личное удобство:
openspec workset create tracking --member ~/openspec/syn-specs --member ~/src/orders-service --tool codeоткрывает планирование и код в одном окне IDE. Не коммитится, ничего не решает — просто удобный вид. - Связывайте PR-ы: в описании каждого implement-PR — ссылка на PR контракта в store. Ревьюер должен видеть, какой версии контракта соответствует реализация.
Кейс «Синтез». Первый же прогон поймал классику: в контракте
Statusбыл enum из трёх значений, а перевозчик присылает семь. Настя увидела это на ревью контракта в store — до того, как тип уехал в NuGet-пакет и в два сервиса. Цена правки: один комментарий в PR. Цена той же правки после релиза пакета: три PR и миграция.
Что дальше
Механика работает end-to-end. Осталось самое сложное — люди: как раскатать процесс на команду так, чтобы через месяц он не умер. Часть 6, финальная.