Часть 4 · ~10 мин

Первая фича через спецификацию

Задача

Настоящая задача из бэклога «Синтеза»: рефанд разрешён только в течение 30 дней после оплаты, и только если заказ не отгружен. Сейчас payments-service разрешает возврат в любом статусе — тот самый инцидент из части 1.

Прогоним её целиком. Предполагается, что openspec init --tools qwen в репозитории уже сделан (часть 2).

Шаг 0. Explore — необязательный, но окупается

qwen> /opsx-explore
qwen> Хочу ограничить окно рефанда. Не уверен, где проверять:
      в Payments или в Orders?

Агент читает код и отвечает по существу: статус заказа живёт в orders-service, но политика рефанда — доменное правило платежей; предлагает проверку в Payments со снапшотом статуса из события OrderShipped, которое Payments уже консьюмит. Ничего не создано, ничего не написано — но решение принято до того, как появился первый артефакт.

Шаг 1. Propose — агент пишет, человек ревьюит

qwen> /opsx-propose limit-refund-window

Агент создаёт openspec/changes/limit-refund-window/ с четырьмя артефактами. Смотрим главный — дельта-спеку specs/refunds/spec.md:

# Delta for Refunds

## ADDED Requirements

### Requirement: Refund window
Возврат ДОЛЖЕН быть отклонён, если с момента оплаты прошло
более 30 календарных дней.

#### Scenario: Окно истекло
- GIVEN заказ оплачен 31 день назад
- WHEN покупатель запрашивает возврат
- THEN возврат отклоняется с кодом refunds.window-expired

### Requirement: Shipped orders are not refundable
Возврат ДОЛЖЕН быть отклонён, если заказ отгружен.

#### Scenario: Заказ отгружен
- GIVEN заказ в статусе Shipped
- WHEN покупатель запрашивает возврат
- THEN возврат отклоняется с кодом refunds.order-shipped

Формат дельты — ADDED / MODIFIED / REMOVED Requirements: спека описывает не «как будет вообще», а что именно меняется относительно текущих спек. Каждое требование подкреплено сценарием GIVEN–WHEN–THEN — это и есть единица ревью.

Здесь работает человек. Даша читает и замечает: а что с частичной отгрузкой? Правит прямо в файле — добавляет сценарий (или просит агента: «добавь сценарий частичной отгрузки»). Три минуты ревью намерения против часа ревью диффа.

Заодно проверяем tasks.md — чек-лист реализации:

## 1. Домен
- [ ] 1.1 RefundPolicy: проверка окна 30 дней от PaidAt
- [ ] 1.2 RefundPolicy: отказ для Shipped/PartiallyShipped
## 2. Приложение
- [ ] 2.1 Коды ошибок refunds.window-expired, refunds.order-shipped
- [ ] 2.2 Обновить обработчик RequestRefundCommand
## 3. Тесты
- [ ] 3.1 Тесты на каждый сценарий спеки

Шаг 2. Валидация — дёшево и до кода

openspec validate limit-refund-window

CLI проверяет структуру: у каждого требования есть сценарий, формат дельт корректен, нумерация задач однозначна. Это займёт секунду и станет PR-гейтом в части 6.

Шаг 3. Apply — агент реализует по чек-листу

qwen> /opsx-apply
      ✓ 1.1 RefundPolicy: окно 30 дней
      ✓ 1.2 RefundPolicy: отказ для отгруженных
      ✓ 2.1 Коды ошибок
      ✓ 2.2 Обработчик RequestRefundCommand
      ✓ 3.1 Тесты: 6 добавлено, все зелёные

Ключевое отличие от «просто попросить агента»: реализация идёт по утверждённому чек-листу, а не по памяти диалога. Если по ходу выяснилось, что дизайн неточен, — правим design.md и продолжаем; артефакты не высечены в камне.

Совет из практики OpenSpec: перед apply очистите контекст (новая сессия). Вся нужная информация уже в артефактах — чистый контекст надёжнее длинного диалога.

Шаг 4. Один PR — спека и код вместе

В PR попадает и openspec/changes/limit-refund-window/, и код, и тесты. Ревьюер начинает со спеки: если намерение верное, дифф читается за минуты — он обязан всего лишь соответствовать сценариям.

Шаг 5. Archive — дельта становится истиной

После мержа:

qwen> /opsx-archive
      ✓ Дельта влита в openspec/specs/refunds/spec.md
      ✓ Изменение перемещено в changes/archive/2026-08-11-limit-refund-window/

Теперь openspec/specs/refunds/spec.md описывает текущее поведение рефандов — включая новые правила. Следующее изменение в этой области начнётся с чтения этой спеки, а не с археологии по чатам. Архив остаётся историей решений: видно, что менялось, когда и почему.

Кейс «Синтез». Олег согласился на один прогон «чтобы доказать, что это бюрократия». Его вердикт после: «propose занял семь минут, из них пять я спорил со сценарием частичной отгрузки. Раньше этот спор случился бы на проде». Обращённых скептиков не бывает — бывают скептики, которым дали поймать ошибку на этапе спеки.

Что дальше

Один сервис — понятно. Но задача «показывать статус доставки в личном кабинете» трогает orders-service, notifications-service и библиотеку Syn.Contracts. Часть 5 — как жить, когда фича пересекает границы репозиториев.