← назад к разделу

Когда разработчик заходит в незнакомый сервис, первый вопрос — «что он вообще делает?». Ответ обычно приходится собирать по кусочкам: читать код, смотреть в базу, расспрашивать коллег. Это теряет время и приводит к ошибкам.

Спецификация — единый документ, который отвечает на этот вопрос. Описывает один Bounded Context в доменных терминах: что умеет сервис, какие у него роли, какие события он публикует, как устроен жизненный цикл сущностей. Бизнес-аналитик, архитектор и разработчик заполняют её совместно; AI-агент использует как источник памяти и генерирует по ней код.

Обязательно

Почему не просто README

README описывает «как запустить». Спецификация описывает «что это такое и почему так устроено».

Без спеки одна и та же вещь называется по-разному в коде, в задачах и на созвонах. Новый разработчик не знает, где граница сервиса — что он должен делать, а что нет. AI-агент не может сгенерировать корректный код, не зная бизнес-правил.

Спецификация решает три проблемы:

  • фиксирует единый язык — одни термины в коде, документах и разговорах;
  • задаёт границу сервиса — что внутри, что снаружи, что на стыке;
  • становится источником для генерации кода, тестов и диаграмм.

Принципы формата

Прежде чем смотреть на структуру, важно понять несколько правил, на которых она держится.

Единица спеки — Bounded Context, не агрегат. Один контекст = одна спека. Если в контексте несколько агрегатов, спека разбивается на корневой файл (секции уровня контекста) и по файлу на агрегат. Разбивать один контекст на несколько спек нельзя: Ubiquitous Language, Context Map, роли — свойства всего контекста.

Домен без техники. Все секции, кроме «Технической реализации», пишутся в доменных терминах. Фреймворки, аннотации, схема базы данных, топики, стек — только в разделе «Техническая реализация». Так спека остаётся понятной бизнесу и не устаревает при смене технологий.

Ничего не дублируется. Каждый факт — в одном месте; остальные ссылаются. События описываются у агрегата-владельца; переходы — в матрице жизненного цикла; ошибки — в командах и бизнес-правилах.

Карточки + строгие таблицы. Команды, запросы, use cases — карточки ### Имя с размеченными полями: читаемо человеку, парсится агентом. Структурные секции (доступ, события, интеграции) — таблицы.

Когда факт нужен двоим

«Ничего не дублируется» — принцип, который ломается на первом же настоящем конфликте: событие OrderConfirmed важно и агрегату-владельцу, который его публикует, и подписчику в другом контексте, для которого это вход. Кто его описывает?

Ответ: описывает владелец, ссылается потребитель. Полное описание события — поля, триггер, область видимости — живёт у агрегата, который его порождает. У подписчика в его спеке стоит строка в таблице интеграций: какое событие он слушает, из какого контекста, что делает в ответ. Это не дубль, потому что описываются разные вещи: там — что случилось, здесь — что мы на это делаем.

ФактПолное описаниеГде ссылка
Публикуемое событиеу агрегата-владельца, в «Доменных событиях»у подписчика — в «Интеграциях», строкой «слушаем X из контекста Y»
Чужое событие, на которое реагируемв «Интеграциях» нашего контекстав матрице переходов агрегата — как триггер перехода
Статус и переходв матрице переходов агрегатав «UI-спецификации» — как это видит пользователь
Бизнес-правилов «Бизнес-правилах» агрегата, с кодомв карточке команды — по коду, а не текстом
Терминв «Ubiquitous Language» корнявезде — просто используется
Рольв «Роли и доступ» корняв матрице доступа агрегата — как столбец

Три правила, которые разрешают почти все спорные случаи:

  • Дублируется текст, не ссылка. Написать «BR-O01» в карточке команды — это ссылка. Переписать формулировку правила в карточку — дубль, который разойдётся.
  • У каждого факта один автор и много читателей. Если два человека правят одно место — граница проведена неверно, и это сигнал не про формат, а про владение контекстом.
  • Когда владелец неочевиден, владельцем становится тот, кто меняет факт. Событие меняет тот, кто его публикует; формат ответа меняет тот, кто отвечает. Правило «владеет тот, кто меняет» снимает большинство споров о том, где описывать.

Отдельный случай — контракт между контекстами. Схема события, которую читают чужие сервисы, одновременно часть спеки владельца и обязательство перед потребителями. Дубля здесь тоже нет: в спеке живёт смысл и поля, в контракте событий — схема, и они связаны гейтом сверки. Кто хозяин имени поля в этой связке — в разделе ниже.

Раскладка файлов

Спека живёт в docs/spec/ и всегда делится на два уровня:

docs/spec/
  <service>-spec.md          # корень: секции уровня контекста
  aggregates/
    <aggregate>.md           # по файлу на агрегат: секции уровня агрегата

Агрегат — всегда в отдельном файле aggregates/<name>.md, даже если он один. На Уровне 1–2, где DDD-агрегатов ещё нет, в этот файл кладут центральную сущность контекста (product.md, notification.md). Структура «корень + aggregates/» единая во всех спеках.

В начало каждого файла добавляют минимальный frontmatter для идентификации чанка при индексации. Для корня — context, bounded-context, level. Для файла агрегата — context, aggregate, level.

Поля context и bounded-context выглядят дублем, но это разные вещи. context — техническое имя, одно и то же в корне и во всех файлах агрегатов: по нему файлы собираются в одну спеку (обычно это имя сервиса — catalog, cabinet). bounded-context — имя контекста на языке домена, как его называют бизнес и глоссарий (Каталог товаров, Bus Ticket Sales); стоит только в корне.

Команды карточка команды операция OpenAPI UseCase и тесты Доменные события таблица событий схема AsyncAPI класс события Доменная модель разделы агрегата агрегат в коде таблицы и ER

Слева раздел спеки, дальше его запись и артефакты, которые из неё выводятся: по этой цепочке код и сверяют со спекой.

Что входит в корневой файл

Корневой файл описывает контекст в целом. Он состоит из одиннадцати разделов:

№РазделЧто внутри
1Bounded Contextмиссия, субдомен (Core/Supporting/Generic), владелец, таблица агрегатов, что внутри/вне границы, стыки
2Интеграции (Context Map)карта контекстов (mermaid) + таблица рёбер + ссылки на контракты (OpenAPI/AsyncAPI)
3Ubiquitous Languageглоссарий контекста; «не путать с»
4Роли и доступроли, общие ABAC-правила, PII; доступ к операциям — в каждом агрегате
5Доменные событияконтракт публикуемого языка: агрегат → внешние события → топик
6Use Casesсквозные сценарии карточками ### UC-N
7ПроцессыSaga/Process Manager: кросс-агрегатные процессы, sequence-диаграммы
8UI-спецификациясвязь статусов с UI, тексты ошибок для пользователя
9Критерии приёмкиGiven / When / Then
10Нефункциональные требованияпроизводительность, доступность, согласованность, безопасность, наблюдаемость — целевые значения без инструментов
11Техническая реализацияединственный технический раздел: контейнеры C2, стек, схема БД (ER + индексы)

Что входит в файл агрегата

Каждый агрегат описывается в семи разделах:

№РазделЧто внутри
1Доменная модельагрегат-корень + сущности, value objects (через инвариант), class-диаграмма
2Жизненный циклстатусы + матрица переходов (команды, события, политики/таймауты) + state-диаграмма
3Доступматрица «операция × роль» + ABAC
4Бизнес-правиласписок BR-<префикс>NN с типом, командой и кодом ошибки
5Командыкарточки: Переход · Вход · Предусловия · Логика · Эмитит · Ошибки
6Доменные событиятаблица «событие — триггер — scope — подписчики»
7Запросыкарточки: Вопрос · Параметры · Возвращает · Логика (источник чтения, фильтр, согласованность)

Как это выглядит заполненным

Таблицы выше описывают, что должно быть в разделах. Ниже — как это выглядит на самом деле, на трёх самых важных артефактах: карточке команды, матрице переходов и бизнес-правилах.

Карточка команды

### Команда: CancelOrder

Переход: DRAFT|CONFIRMED → CANCELLED
Вход: orderId, reason, idempotencyKey
Предусловия: заказ существует; актор владелец заказа или роль operator;
             заказ не отгружен
Логика: проверить BR-O05 (отмена невозможна после отгрузки);
        освободить резерв позиций; если заказ был оплачен — пометить к возврату;
        перевести в CANCELLED, записать причину
Эмитит: OrderCancelled { orderId, reason, refundRequired, cancelledAt }
Ошибки: ORDER_NOT_FOUND, ORDER_ALREADY_SHIPPED (BR-O05),
        ORDER_ALREADY_CANCELLED, FORBIDDEN

Что делает эту карточку рабочей, а не описательной: каждая строка превращается в артефакт. «Вход» — в поля команды, «Переход» — в проверку статуса, «Логика» — в порядок вызовов у обработчика, «Ошибки» — в классы исключений и в ответы контракта, «Эмитит» — в схему события. По этому же списку собирается перечень тестов. Строка «Предусловия» отличается от «Логики» тем, что проверяется до изменения состояния и обычно относится к правам и существованию, а не к бизнес-правилам.

Матрица переходов

Артефакт, который упоминается чаще всех и вызывает больше всего вопросов. Это таблица жизненного цикла: из какого статуса, в какой, по какой команде или событию, при каком условии.

## Жизненный цикл

Статусы: DRAFT, CONFIRMED, PAID, SHIPPED, DELIVERED, CANCELLED

| Из | В | Триггер | Тип | Условие / политика |
|---|---|---|---|---|
| — | DRAFT | CreateOrder | команда | — |
| DRAFT | CONFIRMED | ConfirmOrder | команда | BR-O01, BR-O03 |
| DRAFT | CANCELLED | CancelOrder | команда | — |
| CONFIRMED | PAID | PaymentCaptured | событие | событие из контекста платежей |
| CONFIRMED | CANCELLED | таймаут оплаты | политика | 15 минут без оплаты, планировщик |
| CONFIRMED | CANCELLED | CancelOrder | команда | BR-O05 |
| PAID | SHIPPED | ShipOrder | команда | роль operator |
| SHIPPED | DELIVERED | DeliveryConfirmed | событие | событие из контекста доставки |
| CANCELLED | — | — | — | терминальный, переходов нет |

Колонки объясняются так:

  • Из / В — статусы. Прочерк в «Из» означает создание, прочерк в «В» — терминальное состояние.
  • Триггер — что вызывает переход: имя команды, имя события или описание политики.
  • Тип — команда (кто-то попросил), событие (что-то случилось снаружи), политика (сработало по времени или по правилу без внешнего запроса). Это различение и есть ответ на вопрос, что значит «политики/таймауты»: политика — переход, который делает сама система, без команды и без внешнего события, обычно по истечении срока.
  • Условие / политика — коды бизнес-правил, требуемая роль, срок таймаута, источник события.

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

Бизнес-правила и доступ

## Бизнес-правила

| Код | Правило | Тип | Команда | Ошибка |
|---|---|---|---|---|
| BR-O01 | В заказе хотя бы одна позиция | инвариант | ConfirmOrder | ORDER_EMPTY |
| BR-O03 | Все позиции в наличии на момент подтверждения | предусловие | ConfirmOrder | ITEM_UNAVAILABLE |
| BR-O05 | Отмена невозможна после отгрузки | инвариант | CancelOrder | ORDER_ALREADY_SHIPPED |
| BR-O07 | Сумма заказа равна сумме позиций со скидкой | инвариант | все | — |

## Доступ

| Операция | customer | operator | support | Условие (ABAC) |
|---|---|---|---|---|
| CreateOrder | да | нет | нет | — |
| ConfirmOrder | да | да | нет | customer только свой заказ |
| CancelOrder | да | да | нет | customer только свой и до отгрузки |
| ShipOrder | нет | да | нет | — |
| GetOrder | да | да | да | customer только свой |

Различие «инвариант / предусловие» здесь не формальность: инвариант обязан держаться всегда и проверяется внутри объекта при любом изменении; предусловие проверяется на входе в операцию и может опираться на внешние данные (наличие на складе). Первое живёт в модели, второе — в обработчике, и именно это различие определяет, где ревью ожидает увидеть проверку.

Правило без ошибки (BR-O07) — это инвариант, который нельзя нарушить снаружи: он поддерживается конструкцией, а не проверкой, и потому кода ошибки у него нет.

Несколько важных конвенций

Value Objects описываются через инвариант. Value Object — объект, который узнают по значению, а не по идентификатору (деньги, адрес, период), а инвариант — правило, которое он обязан держать всегда; подробнее в тактических паттернах DDD. Описывают его не через поля и типы (это зона базы данных), а через то, что он защищает. Для sum-типов — варианты: Discount: Percentage | Fixed.

Бизнес-правила кодируются с префиксом агрегата. BR-O01, BR-D01 — чтобы коды не пересекались между агрегатами.

Команды в «Командах» — только публичные. Реакции на события и политики (таймауты) — не отдельные карточки. Они уже отражены в матрице переходов и «Доменных событиях».

Каталога ошибок нет. Коды ошибок — в «Командах» (поле «Ошибки») и «Бизнес-правилах»; тексты для пользователя — в «UI»; HTTP-маппинг — артефакт API, не спеки.

Кросс-агрегатные ссылки — по ID, без внешних ключей. <other>_id — логическая ссылка; это отражается и в class-диаграммах, и в ER.

Уровень зрелости — сколько заполнять

Спека не требует одинаковой глубины для всех сервисов. Глубина определяется уровнем зрелости сервиса:

УровеньАрхитектураЧто меняется в спеке
0 — As-isреверс из кодаснимок «как есть»; not-declared для пробелов; точка отсчёта для миграции
1 — СлоёныйController → Service → RepositoryDDD-агрегатов нет (один «модуль»); «Доменная модель» = ER + таблицы; «Доменные события» и «Процессы» пропускаются с пометкой
2 — Use Case PatternUseCase + Handlerкоманды = UseCase-классы; события — если реально публикуются; CQRS + Read Model — опция уровня
3 — DDD + Hexagonalагрегаты, доменные события, ports/adaptersполная глубина, разбивка по агрегатам

Раздел, неприменимый на текущем уровне, не пропускают молча — пишут заголовок и однострочную пометку: «Не применимо на Уровне 1».

Выглядит это буквально так — заголовок на месте, под ним одна строка:

## 5. Доменные события

Не применимо на Уровне 1: сервис не публикует события, интеграция только синхронная.

## 7. Процессы

Не применимо на Уровне 1: кросс-агрегатных процессов нет.

Почему именно так, а не просто пропустить: пропущенный раздел неотличим от забытого. Читатель (и агент) видит спеку без раздела «Доменные события» и не знает, события не публикуются или их не описали. Пометка отвечает на это за одну строку и заодно объясняет причину — а причина превращается в признак перехода на следующий уровень: как только события появятся, строка станет неправдой, и это заметят.

Формулировка пометки содержит две вещи: уровень и почему. «Не применимо» без причины — та же неопределённость, только с заголовком.

Сколько это страниц

Чтобы понимать, во что ввязываешься, полезны ориентиры по объёму. Порядок величины для одного контекста среднего размера:

УровеньКорневой файлФайл агрегатаВсегоСколько занимает заполнение
0 — as-is2–4 страницы, половина not-declared1–2 страницы4–6 страницчасы генерации плюс день вычитки
1 — слоёный3–5 страниц, три раздела помечены1–2 страницы на сущность5–8 страницдень-два вместе с бизнесом
2 — Use Case Pattern5–8 страниц2–4 страницы8–15 страницдва-три дня
3 — DDD и Hexagonal8–12 страниц4–8 страниц на агрегат15–30 страниц на контекст с двумя-тремя агрегатаминеделя старшего инженера с бизнесом

Что важнее самих чисел — признаки неправильного объёма:

  • Спека уровня 3 короче десяти страниц почти всегда означает, что матрицы переходов и бизнес-правила заполнены формально: «статусы есть, переходы очевидны». Очевидных переходов не бывает.
  • Спека длиннее сорока страниц на один контекст означает одно из двух: в неё утекла техника (схемы, конфигурация, примеры кода) или контекст на самом деле два.
  • Файл агрегата длиннее десяти страниц — сигнал, что агрегат слишком большой; это разговор о границах агрегата, а не о формате.
  • Корневой файл длиннее файлов агрегатов вместе — обычно значит, что в корень уехало то, что относится к агрегатам: команды, правила, доступ.

И оговорка про страницы как меру: считать надо не символы, а заполненные разделы. Спека на шесть страниц со всеми одиннадцатью разделами лучше двадцати страниц, где четыре раздела расписаны, а семь отсутствуют.

Уровень 0 — as-is из кода. Это особый режим: спеку по существующему сервису без бизнес-брифа генерирует скилл ucp-spec-tier-0, читая код, миграции и конфигурацию. Он ставит level: 0, не выдумывает агрегаты там, где код без доменной логики, отсутствующие данные помечает литералом not-declared. Это точка отсчёта для перехода на нужный уровень.

Кто обновляет спеку и что делать при расхождении

Формат описан, а правила ведения — нет, хотя именно на них спека и живёт или умирает.

Обновляет тот, кто меняет поведение. Не аналитик «потом», не отдельная роль хранителя документации. Изменили команду в коде — карточка в спеке правится тем же человеком и в том же изменении. Это единственный порядок, который работает: любая отложенная синхронизация не происходит.

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

Кто утверждает. Зависит от того, что меняется:

Что меняетсяКто решает
Формулировка, опечатка, уточнение без смены смыслаавтор, без обсуждения
Новая команда или запрос внутри контекстаавтор плюс ревью команды
Бизнес-правило, статус, переходвместе с владельцем домена: это решение о продукте
Публикуемое событие или контракт наружусогласование с потребителями, потому что это обязательство
Границы контекста, состав агрегатовархитектурное решение с записью причины

Расхождение спеки и кода — дефект, а не долг. Нашли на ревью: чинится в том же изменении, и чинится одно из двух — либо код под спеку, либо спека под код с записанной причиной. Формулировка «поправим спеку следующим изменением» не принимается по той же причине, по которой не принимается красная сборка: следующего изменения не бывает.

Как расхождение обнаруживается — три слоя, от машинного к человеческому:

  • Гейты в сборке сверяют формальное: команды из карточек против классов, коды ошибок против исключений, события против схем контракта, статусы из матрицы против перечисления в коде. Это ловит большинство расхождений, потому что типичное расхождение и есть «добавили операцию, карточку не тронули».
  • Ревью по карточке. Смысловое расхождение (логика в обработчике не совпадает со строкой «Логика», правило проверяется не там, где положено) машина не видит. Поэтому на ревью карточка и код смотрят рядом, а не только код.
  • Периодическая сверка. Раз в несколько недель прочитать спеку и код целиком и выписать расхождения по смыслу. Результат — задачи с приоритетом, а не заметки.

Что делать, если спека уже отстала сильно. Не переписывать целиком: сгенерировать снимок «как есть» и сравнить с тем, что было записано. Разница делится на три части — что изменилось осознанно (обновить), что изменилось случайно (это дефекты, завести задачи), чего в коде нет, а в спеке есть (удалить или признать невыполненным обязательством). Порядок разбора именно такой: вторая часть — самое ценное, что даёт эта операция.

Дополнительно: при первом чтении можно пропустить

Глубже: договор между спекой и контрактом API: кто хозяин имени полярасширенное

«Из Commands получается OpenAPI, из Domain Events AsyncAPI» требует правил соответствия, иначе через месяц в контракте поле customer_id, в спеке «покупатель», в коде buyerId, и никто не знает, кто прав.

Хозяин имени это спека. Термин из Ubiquitous Language контекста определяет имя поля во всех артефактах: в карточке команды, в схеме OpenAPI, в событии, в коде и в таблице; перевод с русского термина на английский идентификатор один и записан в глоссарии, о чём статья про стратегические паттерны DDD. Контракт API не придумывает имён, он транслирует.

Правила соответствия. Карточка команды даёт операцию: имя операции равно имени команды, поля «Входа» становятся телом или параметрами, «Ошибки» становятся ответами с кодами (сопоставление кода ошибки со статусом это артефакт API, и оно живёт в одном месте, а не в спеке), «Переход» задаёт, какие статусы ресурса операция принимает. Карточка запроса даёт GET с параметрами из «Параметров» и схемой из «Возвращает». Событие из «Доменных событий» даёт схему сообщения в AsyncAPI с теми же полями и именем топика из таблицы. Всё, чего нет в карточке, в контракте появиться не может: поле «для удобства фронта» сначала попадает в спеку.

Когда контракт меняется, а спека нет. Это расхождение, и его ловит гейт сверки операций и полей; чинят спеку первой (документ изменения с классификацией: совместимое или ломающее), потом контракт. Обратное направление, спека изменилась, контракт нет, ловится тем же гейтом, и здесь ещё проверяют совместимость контракта с предыдущей версией инструментом сравнения спецификаций, о чём статья про API-first.

Что спека не решает. Версионирование URL, формат дат и денег, пагинация, заголовки идемпотентности и трассировки: это соглашения API, общие для всех сервисов, и они живут в стандарте API, а спека на них ссылается. Так у спеки один хозяин имён и смысла, у стандарта хозяин формы, и спор «как назвать поле» решается один раз в глоссарии.

Глубже: сколько стоит методология и когда окупаетсярасширенное

Три статьи требуют инвестиций («неделя старшего инженера», «день-два на сервис», «сотни правил»), и ни одна не складывает их вместе. Сложим по порядку величины.

Заведение. Корпус правил и скиллы берут готовые, установка это часы; настройка под проект (какие правила включить, гейты, базовые списки нарушений) это день-два инженера, который понимает методологию. Спека контекста на уровне 0 генерируется из кода за часы и правится глазами за день; спека уровня 3 для сложного контекста с агрегатами это неделя старшего инженера вместе с бизнесом, и половина этой недели это Event Storming, который окупился бы и без методологии.

Поддержание. Каждое изменение поведения стоит карточку и документ изменения: от десяти минут для поля до часа для новой операции, то есть 5–10 % времени разработки; гейты в сборке бесплатны после заведения; сверка раз в месяц это час. Обучение нового участника команды: неделя до самостоятельной работы по спеке против месяца чтения кода без неё.

Что на другой чаше. Время на выяснение «как это работает» у каждого, кто трогает сервис; переделки после ревью, когда правило нарушили и узнали поздно; регрессии от изменений, которые задели соседнее; и цена работы с агентом без модели, когда каждая сессия изобретает устройство заново, и приёмка становится дороже генерации. Эти статьи расходов обычно 20–40 % времени команды на сервисе старше года, и это измеримо по журналу задач.

Точка окупаемости. Сервис, который живёт больше года, меняется каждую неделю и в котором работает больше одного человека или один человек с агентом: окупается за первые месяцы. Сервис на квартал с одним разработчиком: не окупается, о чём раздел «когда не брать» в статье про уровень 0. Считать это на своём сервисе просто: неделя заведения плюс десять процентов сверху против доли времени, которое сейчас уходит на выяснение, переделки и регрессии; вторая цифра почти всегда больше, и её редко кто считал.

Коротко

  • Спека описывает один Bounded Context в доменных терминах; бизнес-правила, роли, события, жизненный цикл. Файлы делятся на корень (уровень контекста) и aggregates/<name>.md (уровень агрегата); структура единая для всех уровней зрелости.
  • Всё, что не «Техническая реализация», пишется без упоминания фреймворков, аннотаций и схемы базы данных. Команды, запросы, use cases — карточки ### Имя; структурные секции — нормализованные таблицы. Бизнес-правила кодируются с префиксом агрегата (BR-O01); каждый факт — в одном месте. Глубина спеки = уровень зрелости сервиса (0–3); неприменимые разделы помечаются, не пропускаются.
  • Хозяин имён это спека через глоссарий: команда даёт операцию, запрос даёт GET, событие даёт схему сообщения; поле, которого нет в карточке, в контракт не попадает; форма (версии, даты, пагинация) живёт в стандарте API.
  • Цена методологии: день-два настройки, от часов до недели на спеку контекста, 5–10 % времени на ведение; против 20–40 % на выяснение, переделки и регрессии в сервисе старше года; окупается за месяцы там, где сервис живёт и меняется.
  • Факт, нужный двоим, описывает владелец, а потребитель ссылается: событие полностью — у агрегата-владельца, у подписчика строка в интеграциях; владеет тот, кто меняет.
  • Карточка команды рабочая, когда каждая её строка превращается в артефакт: вход в поля, переход в проверку статуса, логика в порядок вызовов, ошибки в исключения и ответы, «эмитит» в схему события.
  • Матрица переходов — таблица «из, в, триггер, тип, условие»: политика это переход, который система делает сама по времени, а отсутствующая пара означает запрещённый переход и превращается в тест.
  • Неприменимый раздел помечают заголовком и строкой с уровнем и причиной: пропущенный раздел неотличим от забытого, а причина потом становится признаком перехода на следующий уровень.
  • Объём для контекста среднего размера: 4–6 страниц на уровне 0, 8–15 на уровне 2, 15–30 на уровне 3; мерят заполненные разделы, а спека уровня 3 короче десяти страниц означает формально заполненные матрицы.
  • Спеку обновляет тот, кто меняет поведение, и в том же изменении; расхождение — дефект, который чинят сразу, а ловят гейты, ревью по карточке и периодическая сверка.

Что почитать дальше