Перейти к содержимому
Программа SDDSDD.2 Как писать спецификации

Технический план, модель данных и контракты

Как перейти от спецификации («что и зачем») к техническому плану («как»): архитектура и стек с обоснованием, модель данных, контракты (сигнатуры функций, JSON Schema, OpenAPI), исследование вариантов и записи архитектурных решений. DDD как скелет плана и мера деталей для ИИ-агента — на примере записи на поток онлайн-школы.

УРОКsdd/sdd-2-specs/02-design-and-plan.mdx2 чУрок 2 из 3
После урока вы сможете
  • Отделять решения плана («как») от требований спецификации («что и зачем») и собирать план по шаблону Spec Kit
  • Описывать модель данных и контракты — сигнатуры функций, JSON Schema, OpenAPI — так, чтобы агенту не приходилось додумывать интерфейсы
  • Вести исследование вариантов (research.md) и записи архитектурных решений (ADR)
  • Строить план на скелете DDD — контексты, агрегаты, единый язык — и давать агенту столько деталей, сколько нужно

В уроке SDD.2.1 мы написали спецификацию записи на поток: требования с идентификаторами, сценарии «Дано / Когда / Тогда», границы и открытые вопросы. Отдадим её агенту со словами «реализуй». Через десять минут он вернётся с работающим кодом — и с десятком решений, о которых нас никто не спросил:

  • взял веб-фреймворк и базу данных, которых в проекте нет, и добавил три зависимости;
  • назвал класс Order, а поле — user_id, хотя в глоссарии Enrollment и student_id;
  • хранит цену как float и где-то округляет;
  • отменяет неоплаченные записи таймером внутри процесса — после перезапуска сервера таймеры пропадают;
  • придумал HTTP-адреса и формат ошибок, а команда фронтенда уже сделала другие по старой договорённости.

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

Технический плантехнический планEN technical plan, implementation planДокумент о том, как реализовать спецификацию: архитектура и границы модулей, стек с обоснованием, модель данных, контракты, принятые решения и риски. В Spec Kit это plan.md, в OpenSpec — design.md. Спецификация отвечает на вопросы «что и зачем», план — на вопрос «как». — место, где решения «как» принимает человек, явно и до кода: из каких модулей состоит система, на чём она написана и почему, какие у неё данные и интерфейсы, что выбрали из нескольких вариантов и чем за это заплатили. План читается за 15 минут, а не за час, как дифф, — и ошибку в нём исправить дешевле всего. В этом уроке разберём, из чего состоит план, как записывать модель данных, контракты, исследования и решения, где в плане DDD и сколько деталей давать агенту.

От «что» к «как»: где кончается спецификация

Заголовок раздела «От «что» к «как»: где кончается спецификация»

Spec Kit формулирует принцип прямо в README: сначала определите что и зачем, потом решайте, как это строить. Шаблон спецификации требует от агента сосредоточиться на том, что нужно пользователям и зачем, и не писать о том, как это реализовать: никакого стека, API и структуры кода. В OpenSpec есть удобная проверка: если реализацию можно поменять, не меняя поведения, видимого снаружи, — этому не место в спецификации.

УтверждениеГде ему местоПочему
Записей «ожидает оплаты» и «подтверждена» не больше вместимости потокаспецификацияправило бизнеса, его подтверждает методист
Неоплаченная запись отменяется через 48 часов после записиспецификацияповедение, видимое студенту
Отмена с опозданием не больше 5 минут допустимаспецификация (нефункциональное требование)измеримое свойство, о нём договариваются с бизнесом
Сроки проверяет вызов expire_unpaid(now) по расписанию раз в 5 минутпланспособ выполнить требование; можно заменить, не меняя поведения
Записи на поток — внутри агрегата Cohortпланустройство модели
Деньги — Money, сумма в копейках целым числомплан (и конституция)решение о представлении данных
POST /cohorts/{cohort_id}/enrollments, ответ 409 при нехватке местплан, контрактыинтерфейс для других команд

Граница не всегда очевидна. «Ответ на запись приходит за 300 мс» — требование (студент это чувствует), а «кэш свободных мест в памяти» — способ его выполнить. Если сомневаетесь, спросите: «Кто будет спорить с этой фразой — методист или разработчик?» Методист — спецификация, разработчик — план.

Чтобы не изобретать формат, возьмём шаблон Spec Kit — открытого набора инструментов SDD от GitHub (подробно — в уроке SDD.3.1). Ниже — состав по файлам templates/plan-template.md и templates/commands/plan.md в репозитории github/spec-kit на октябрь 2026; шаблоны меняются, сверяйтесь с актуальной версией.

Раздел plan.mdЧто в нёмПример для онлайн-школы
Summary (кратко)главное требование и выбранный подход в двух-трёх предложениях«Записи и сроки живут в агрегате Cohort контекста Набор; Оплата узнаёт о записи из события»
Technical Context (технический контекст)язык и версия, основные зависимости, хранение, тесты, платформа, тип проекта, цели по производительности, ограничения, масштабPython 3.11, только стандартная библиотека, pytest, хранение в памяти в первой итерации
Constitution Check (проверка конституции)контрольная точка: план не нарушает принципов проекта; проверяется до исследования и после проектирования«тесты сначала — да; новых зависимостей — нет»
Project Structure (структура проекта)дерево документов фичи и дерево исходного кода с реальными путямиschool/enrollment/, tests/acceptance/
Complexity Tracking (учёт сложности)заполняется, только если план нарушает конституцию: что нарушено, зачем, почему простой вариант не подошёл«четвёртый пакет нужен, потому что…»

Всё, что неизвестно, шаблон велит помечать NEEDS CLARIFICATION — так же, как маркер [УТОЧНИТЬ: …] в нашей спецификации. А команда планирования работает в две фазы и рядом с plan.md создаёт ещё несколько файлов:

flowchart LR
S["spec.md<br/>что и зачем"] --> P["plan.md<br/>как"]
K["constitution.md<br/>принципы"] -.->|"проверка"| P
P --> R["research.md<br/>фаза 0: варианты<br/>и решения"]
R --> D["data-model.md<br/>сущности, поля,<br/>состояния"]
R --> C["contracts/<br/>интерфейсы"]
R --> Q["quickstart.md<br/>как проверить"]
D --> T["tasks.md<br/>следующий шаг"]
C --> T
Q --> T
Артефакты планирования в Spec Kit: фаза 0 — исследование, фаза 1 — модель данных, контракты и сценарий проверки; задачи — отдельная команда
  • Фаза 0, research.md. Каждое неизвестное из технического контекста превращается в вопрос для исследования, ответ записывается в формате «решение — обоснование — рассмотренные варианты».
  • Фаза 1, data-model.md, contracts/, quickstart.md. Сущности с полями, связями, правилами проверки и переходами состояний; интерфейсы, которые видят другие (если они есть); короткая инструкция, как запустить и проверить фичу целиком, — без кода реализации.
  • tasks.md план не создаёт: это следующий шаг, ему посвящён урок SDD.2.3.

В других инструментах те же идеи называются иначе. В OpenSpec технический подход и архитектурные решения изменения живут в design.md рядом с proposal.md, дельтами требований и tasks.md. Kiro, по описанию в его репозитории на GitHub, раскладывает спецификацию на требования, проектные решения и задачи. А в командах без всяких инструментов то же самое называют дизайн-документомдизайн-документEN design documentДокумент на несколько страниц до начала разработки: задача, ограничения, метрики, варианты решения, оценки нагрузки и стоимости, риски. Его обсуждают с командой и по нему потом проверяют, что построили.. Названия разные, суть одна: решения «как» записаны отдельно от «что» и до кода.

Выбор технологии без причины — приглашение агенту его «улучшить». Манифест Spec Kit требует, чтобы у каждого выбора было записанное обоснование, а каждое архитектурное решение возводилось к конкретному требованию. Удобно писать стек таблицей:

РешениеПочемуСвязь
Python 3.11, только стандартная библиотекакоманда знает Python; модель Набора не требует внешних пакетовконституция, принцип простоты
pytest, приёмочные тесты по сценариям SC-001…сценарии спецификации прямо превращаются в тестыконституция, «тесты сначала»
хранение в памяти (InMemoryCohortRepository) в первой итерацииправила проверяются без базы; база — отдельной задачейresearch R-2
время — параметр now, а не datetime.now() внутри моделиправила 48 часов и 7 суток проверяются тестами без ожиданияREQ-005…REQ-007

Последняя строка — пример решения, которое агент почти никогда не примет сам: модели чаще всего пишут datetime.now() прямо в методе, и тест на «48 часов» становится невозможен без подмены времени.

Если вы прошли модули DDD.1 и DDD.2, у вас уже есть половина плана. Модель предметной области — готовый скелет, на который нанизываются технические решения.

flowchart TB
subgraph DDD["Модель (программа DDD)"]
  BC["Ограниченные контексты<br/>Набор, Оплата, Уведомления"]
  AG["Агрегаты<br/>Cohort: записи и лист ожидания"]
  UL["Единый язык<br/>глоссарий домена"]
  SD["Типы поддоменов<br/>core, supporting, generic"]
end
subgraph PLAN["Технический план"]
  M["Пакеты и границы модулей"]
  TX["Границы транзакций<br/>и блокировок"]
  N["Имена в контрактах,<br/>коде и тестах"]
  DEP["Глубина проработки:<br/>богатая модель или адаптер"]
end
BC --> M
AG --> TX
UL --> N
SD --> DEP
Что план берёт из модели предметной области

Ограниченные контексты → модули. Ограниченный контекстограниченный контекстEN bounded contextЯвная граница, внутри которой одна модель предметной области и её единый язык однозначны и согласованы. За границей те же слова могут значить другое. Границу закрепляют командой, частью приложения, кодом и данными; перевод между контекстами делают явно. — граница, внутри которой у модели один язык (урок DDD.2.1). В плане это пакеты school/enrollment/, school/payment/, school/notifications/ и правило: контекст обращается к соседу только через его открытый интерфейс или события, а не через чужие классы и таблицы. Для записи на поток план прямо перечисляет, какие контексты затронуты и что каждый делает.

Агрегаты → границы согласованности. АгрегатагрегатEN aggregateКластер сущностей и объектов-значений, который меняется только целиком через корень и после каждой операции соблюдает свои инварианты. Граница агрегата — граница транзакции: одна транзакция меняет один агрегат. В онлайн-школе — поток с записями и листом ожидания. (aggregate) — группа объектов, которую меняют как одно целое: снаружи обращаются только к корню, а корень следит за правилами всей группы. Подробно они разбираются в уроке DDD.3.2, а для плана важно одно следствие: одна команда меняет один агрегат. Правило «записей не больше вместимости» касается всех записей потока сразу, поэтому записи и лист ожидания живут внутри агрегата Cohort, а два одновременных запроса на последнее место ловит оптимистическая блокировкаоптимистическая блокировкаEN optimistic locking, optimistic concurrency controlСпособ поймать параллельные изменения без блокировки на время работы: у агрегата есть номер версии, при сохранении хранилище сверяет его с текущим. Версия выросла — кто-то успел раньше, сохранение отклоняется (ConcurrencyError), команду повторяют на свежих данных. по версии потока. Это не деталь реализации, а решение с последствиями — ему место в ADR (пример ниже).

Единый язык → имена. План, контракты и код говорят словами глоссария (урок DDD.1.2): Cohort, Enrollment, enroll, StudentEnrolled, а не Group, Order, process. Новое понятие появляется сначала в глоссарии и только потом в плане. Для агента это важнее, чем для человека: без явного словаря он выберет самые частые в интернете имена.

Типы поддоменов → глубина плана. Набор — основной поддомен (урок DDD.1.3), и план для него подробный: агрегат, инварианты, события, тест на каждое правило. Оплата — типовой поддомен, и в плане для неё одна строка: «счёт выставляет платёжная система, мы пишем адаптер и переводим её ответы в событие PaymentReceived». Тратить страницы плана на то, что вы покупаете готовым, незачем.

Модель данныхмодель данныхEN data modelОписание сущностей фичи: поля, связи, правила проверки из требований и переходы состояний. В Spec Kit — файл data-model.md. Это не схема таблиц базы, а словарь и инварианты, на которых строится код. в смысле Spec Kit — не схема таблиц, а описание сущностей фичи: поля, связи, правила проверки из требований и переходы состояний. Для записи на поток data-model.md может выглядеть так:

СущностьВидПоляПравила (откуда)
Cohort (поток)агрегат, кореньcohort_id, course_id, starts_at, capacity, price, version, записидействующих записей не больше capacity (REQ-001, REQ-002); запись закрыта с starts_at (REQ-004)
Enrollment (запись на поток)сущность внутри Cohortenrollment_id, student_id, enrolled_at, statusу студента одна действующая запись на поток, отменённая не мешает записаться снова (REQ-003); 48 часов без оплаты — «отменена» (REQ-005)
Money (деньги)объект-значениеamount (копейки, int), currencyцелое число копеек и валюта (REQ-009); складываются только в одной валюте
EnrollmentCancelled (ЗаписьОтменена)событиеenrollment_id, cohort_id, reason, refund, occurred_atпричина — отмена студентом или истёк срок оплаты (REQ-008); refund — полная цена или ноль (REQ-006, REQ-007)

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

Контрактконтракт (интерфейса)EN interface contractДоговорённость о том, на что может опираться другая сторона: входы и выходы с форматами, ошибки и побочные эффекты, гарантии (идемпотентность, порядок событий), версии и совместимость. Формы — сигнатуры функций, JSON Schema, OpenAPI; в Spec Kit — папка contracts/. — договорённость о том, на что может опираться другая сторона: другой модуль, другая команда, фронтенд, внешняя система или агент, который пишет код «по ту сторону». Руководство Spec Kit о разработке через контракты перечисляет, что контракт должен определять:

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

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

Форма контракта зависит от интерфейса. Три самые частые:

Сигнатуры функций — для модуля или библиотеки внутри одного приложения. В Python их удобно записать протоколом (typing.Protocol): класс без реализации, у которого есть только методы с типами и докстрингами.

from datetime import datetime
from typing import Protocol
from school.enrollment.money import Money
class EnrollmentService(Protocol):
"""Контракт прикладного сервиса Набора (contracts/enrollment_service.py)."""
def enroll(self, cohort_id: str, student_id: str, now: datetime) -> str:
"""Записать студента на поток и вернуть id записи (статус «ожидает оплаты»).
Ошибки: NoFreeSeats — мест нет, AlreadyEnrolled — уже записан, EnrollmentClosed — поток стартовал.
Событие: StudentEnrolled.
"""
def cancel(self, cohort_id: str, enrollment_id: str, now: datetime) -> Money:
"""Отменить запись по просьбе студента и вернуть сумму возврата: полная цена, если до старта 7 суток
и более, иначе ноль. Событие: EnrollmentCancelled с причиной «отмена студентом».
"""
def expire_unpaid(self, cohort_id: str, now: datetime) -> list[str]:
"""Отменить записи, не оплаченные за 48 часов от момента записи; вернуть их id."""

JSON SchemaJSON SchemaEN JSON SchemaСловарь для описания и проверки структуры JSON-документов: обязательные поля, типы, допустимые значения, форматы. Подходит для контрактов событий и сообщений между контекстами; на нём же основаны схемы данных в OpenAPI 3.1. — для событий и сообщений между контекстами. Оплата узнаёт об отмене записи из события EnrollmentCancelled и возвращает деньги; схема фиксирует, какие поля в событии будут всегда:

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "EnrollmentCancelled",
"description": "ЗаписьОтменена: запись больше не занимает место; Оплата возвращает refund_amount",
"type": "object",
"required": ["event_id", "enrollment_id", "cohort_id", "student_id", "reason",
"refund_amount", "currency", "occurred_at"],
"properties": {
"event_id": {"type": "string", "description": "уникален; повторная доставка приходит с тем же id"},
"enrollment_id": {"type": "string"},
"cohort_id": {"type": "string"},
"student_id": {"type": "string"},
"reason": {"type": "string", "enum": ["cancelled_by_student", "payment_expired"]},
"refund_amount": {"type": "integer", "minimum": 0, "description": "сумма возврата в копейках, 0 — без возврата"},
"currency": {"type": "string", "enum": ["RUB"]},
"occurred_at": {"type": "string", "format": "date-time"}
},
"additionalProperties": false
}

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

OpenAPIOpenAPIEN OpenAPI Specification (OAS)Открытый стандарт машиночитаемого описания HTTP API в YAML или JSON: пути, параметры, тела запросов и ответов, коды ошибок. По описанию строят документацию, клиентов, серверные заготовки и тесты. Развивается OpenAPI Initiative (Linux Foundation). — для HTTP API, которым пользуются фронтенд или внешние системы. Это открытый стандарт машиночитаемого описания API в YAML или JSON; по нему строят документацию, клиентов и тесты. Фрагмент:

openapi: 3.1.0
info:
title: Набор онлайн-школы
version: 1.0.0
paths:
/cohorts/{cohort_id}/enrollments:
post:
summary: Записать студента на поток
parameters:
- name: cohort_id
in: path
required: true
schema: {type: string}
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [student_id]
properties:
student_id: {type: string}
responses:
'201':
description: Запись создана, статус PENDING_PAYMENT
'409':
description: Отказ — NoFreeSeats, AlreadyEnrolled или EnrollmentClosed; код ошибки в теле ответа

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

Когда у вопроса несколько разумных ответов, план не выбирает «на глаз». Исследование вариантовисследование вариантов (research)EN technical researchШаг планирования, на котором до проектирования снимают неизвестные: сравнивают библиотеки и подходы, проверяют ограничения. В Spec Kit итог пишется в research.md в виде «решение — обоснование — рассмотренные варианты». — шаг, на котором неизвестные из технического контекста превращаются в вопросы, а ответы записываются в research.md. Формат Spec Kit простой — решение, обоснование, рассмотренные варианты:

## R-1. Как отменять неоплаченные записи через 48 часов
**Решение:** прикладной сервис раз в 5 минут вызывает `cohort.expire_unpaid(now)` для потоков,
где есть записи «ожидает оплаты».
**Обоснование:** время приходит параметром, поэтому правило проверяется тестом без ожидания; работа
переживает перезапуск сервера. Опоздание отмены до 5 минут подтвердил менеджер набора — это добавлено
в спецификацию нефункциональным требованием.
**Рассмотренные варианты:**
- таймер на каждую запись в памяти процесса — пропадает при перезапуске, трудно тестировать;
- «ленивая» проверка срока при каждом обращении к потоку — место может часами висеть занятым,
если поток никто не открывает;
- отложенные сообщения через брокер очередей — новая инфраструктура ради одной функции,
противоречит принципу простоты из конституции.

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

Исследование удобно поручать агенту: сравнить библиотеки, прочитать документацию, оценить ограничения. Манифест Spec Kit так и описывает процесс: исследовательские агенты собирают контекст о совместимости, производительности и безопасности. Но результат — черновик. Модели уверенно называют несуществующие функции и устаревшие версии библиотек, поэтому ссылки и версии в research.md проверяет человек.

research.md относится к одной фиче. Но некоторые решения переживут её и будут влиять на все следующие: граница агрегата, способ хранения денег, работа со временем. Для них есть запись архитектурного решениязапись архитектурного решения (ADR)EN Architecture Decision RecordКороткий документ об одном значимом решении: статус, контекст, решение, последствия (шаблон Майкла Найгарда, 2011). Старые ADR не переписывают, а заменяют новыми; журнал ADR объясняет людям и агентам, почему система устроена именно так. (Architecture Decision Record, ADR) — короткий документ об одном решении. Самый известный шаблон предложил Майкл Найгард в 2011 году, в нём пять частей: заголовок, статус, контекст, решение, последствия.

# ADR-0001. Поток — граница согласованности для записей и листа ожидания
**Статус:** принято, 2026-10-06
**Контекст.** Правила «записей не больше вместимости», «нельзя записаться дважды» и «место — первому
в листе ожидания» касаются всех записей одного потока сразу. Если менять записи по отдельности,
два одновременных запроса могут дважды занять последнее место.
**Решение.** Записи (Enrollment) и лист ожидания (Waitlist) — части агрегата Cohort, менять их можно
только через его методы. Одна команда меняет один поток. Параллельные изменения ловит оптимистическая
блокировка по полю version.
**Последствия.**
- (+) Все инварианты Набора проверяются в одном месте и покрываются юнит-тестами без базы данных.
- (−) Поток загружается целиком со всеми записями; при вместимости до 30 мест это не проблема.
- (−) Операции над двумя потоками сразу потребуют отдельного решения: одна команда — один агрегат.

Советы из сборника шаблонов ADR Джоэла Паркера Хендерсона: одна запись — одно решение; обоснование важнее самого решения; у записи есть дата; принятые записи не переписывают — их дополняют новой информацией с датой, а если решение изменилось, пишут новый ADR, а старому ставят статус «заменено ADR-000N». (Там же автор честно признаёт, что многим командам на практике удобнее дополнять старые записи, — главное, чтобы было видно, что и когда добавлено.) Журнал ADR лежит в репозитории (например, docs/adr/0001-cohort-aggregate.md), и по нему через год можно понять, почему система устроена именно так.

research.mdADR
Живётв папке фичив журнале решений проекта
Отвечает на вопросчто выбрать для этой фичипочему система устроена так
Форматвопрос, решение, обоснование, вариантыстатус, контекст, решение, последствия
Меняетсявместе с фичейникогда: устаревшую запись заменяют новой

Последняя строка ADR из примера выше — «операции над двумя потоками потребуют отдельного решения» — не случайна. Именно такую операцию, перенос записи на другой поток, вы будете проектировать в домашней работе этого модуля.

Здесь две ошибки, и обе дорогие.

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

Слишком много — агент утонет. План на 40 страниц с телами функций и полными тестами плох по трём причинам. Он съедает контекстное окно, и важные ограничения теряются среди неважных. Код в плане устаревает раньше, чем его реализуют, и агент старательно копирует устаревшее. А человек такой план не прочитает — значит, не проверит. Шаблоны Spec Kit поэтому прямо требуют держать план высокоуровневым и читаемым, а подробные алгоритмы и примеры кода выносить в отдельные файлы; quickstart.md по шаблону не должен содержать кода реализации.

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

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

Агент сам подсказывает, где план неудачен. Если он задаёт один и тот же вопрос в каждой сессии — ответа нет в плане. Если игнорирует часть плана — план слишком длинный или противоречит сам себе. Если с каждым запуском выбирает разные решения одного вопроса — вопрос не закрыт.

Соберём всё вместе. Так может выглядеть plan.md для записи на поток — сокращённо, со ссылками на соседние файлы:

# План: запись на поток
**Спецификация:** specs/001-enrollment/spec.md · **Дата:** 2026-10-06
## Кратко
Записи, сроки и отмены живут в агрегате Cohort контекста «Набор». Оплата узнаёт о записи
и отмене из событий StudentEnrolled и EnrollmentCancelled. Первая итерация — доменная модель и прикладной сервис, без HTTP и базы.
## Технический контекст
- Язык: Python 3.11; зависимости: только стандартная библиотека
- Хранение: InMemoryCohortRepository; база данных — следующей фичей (R-2)
- Тесты: pytest; приёмочные тесты по сценариям SC-001…SC-008; нагрузочный SC-009 — отдельной задачей
- Ограничения: время только через параметр now; деньги — Money в копейках
- Масштаб: до 30 мест в потоке, до 200 потоков одновременно
## Проверка конституции
- [x] Тесты сначала: приёмочные тесты пишутся до кода
- [x] Имена только из docs/glossary.md
- [x] Новых зависимостей нет
## Контексты
| Контекст | Пакет | Роль в фиче |
|---|---|---|
| Набор (core) | school/enrollment/ | Cohort, Enrollment, правила записи, сроков и отмены |
| Оплата (generic) | school/payment/ | по StudentEnrolled выставляет Invoice, по EnrollmentCancelled возвращает refund_amount |
| Уведомления (generic) | school/notifications/ | письма по событиям; в этой фиче без изменений |
## Модель данных — data-model.md
## Контракты — contracts/enrollment_service.py, contracts/enrollment_cancelled.schema.json
## Решения — research.md (R-1, R-2), docs/adr/0001-cohort-aggregate.md
## Структура
school/enrollment/: money.py, model.py, events.py, repository.py, service.py
tests/acceptance/: test_enrollment.py (по одному тесту на сценарий)
## Риски
- Поток могут перенести на другую дату — в спецификации это [УТОЧНИТЬ], вне рамок; пока срок
7 суток считается от текущего starts_at потока

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

Контракт полезен, только если реализация ему следует. Часть проверки автоматизируется в несколько строк: модуль inspect из стандартной библиотеки умеет читать сигнатуры функций. Сравним черновик агента с протоколом EnrollmentService:

from __future__ import annotations # аннотации не вычисляются: класс Money для проверки не нужен
import inspect
from datetime import datetime
from typing import Protocol
class EnrollmentService(Protocol):
"""Контракт прикладного сервиса Набора (сокращённо, докстринги — как выше)."""
def enroll(self, cohort_id: str, student_id: str, now: datetime) -> str: ...
def cancel(self, cohort_id: str, enrollment_id: str, now: datetime) -> Money: ...
def expire_unpaid(self, cohort_id: str, now: datetime) -> list[str]: ...
def contract_violations(contract, implementation) -> list[str]:
"""Сравнивает реализацию с контрактом: есть ли каждый метод и совпадают ли имена параметров."""
problems = []
for name, method in inspect.getmembers(contract, inspect.isfunction):
if name.startswith("_"):
continue # служебные методы Protocol не часть контракта
impl = getattr(implementation, name, None)
if impl is None:
problems.append(f"нет метода {name}")
continue
want = list(inspect.signature(method).parameters)
got = list(inspect.signature(impl).parameters)
if got != want:
problems.append(f"{name}: параметры {got}, а по контракту {want}")
return problems
class AgentDraft:
"""Черновик сервиса, который агент написал по памяти, не открыв контракт."""
def enroll(self, cohort_id, user_id, now):
...
def cancel(self, cohort_id, enrollment_id, now):
...
for problem in contract_violations(EnrollmentService, AgentDraft):
print("✗", problem)
✗ enroll: параметры ['self', 'cohort_id', 'user_id', 'now'], а по контракту ['self', 'cohort_id', 'student_id', 'now']
✗ нет метода expire_unpaid

Две находки — две типичные ошибки агента: имя из «интернета» (user_id) вместо слова глоссария и забытый метод. Такую проверку можно положить в тесты, и агент, запустив их, исправится сам. В настоящем проекте ту же работу для типов делает статический анализатор (например, mypy проверяет, что класс соответствует протоколу), а для HTTP — тесты, сверяющие ответы сервера с OpenAPI-описанием.

  1. Допишите в research.md вопрос R-2 «Где хранить потоки в первой итерации»: решение, обоснование и не меньше двух отвергнутых вариантов.
  2. Напишите ADR-0002 «Деньги — сумма в копейках целым числом и валюта». В последствиях укажите хотя бы один минус.
  3. Отдайте агенту спецификацию из урока SDD.2.1 и план выше с просьбой: «Не пиши код. Перечисли решения, которые тебе пришлось бы принять самому, потому что их нет в плане». Каждый пункт ответа — либо строка в плане, либо осознанное «оставляем агенту».
Проверь себя6 вопросов
1 Какое утверждение место в техническом плане, а не в спецификации?
2 Что должен определять контракт интерфейса? Выберите все верные пункты.Можно выбрать несколько ответов
3 Решение из ADR-0001 устарело: команда решила хранить лист ожидания отдельно от потока. Как поступить с журналом решений?
4 В черновике агента метод enroll(self, cohort_id, user_id, now), а в глоссарии — «Студент → Student». Какая защита работает надёжнее всего?
5 План на 40 страниц содержит тела всех функций и полные тексты тестов. Что здесь главная проблема?
6 Почему в плане записи на поток записи и лист ожидания помещены внутрь агрегата Cohort?
  • Спецификация отвечает на вопросы «что и зачем», технический план — на вопрос «как». Проверка: если реализацию можно поменять, не меняя видимого поведения, — это в план, а не в спецификацию.
  • План по шаблону Spec Kit (на октябрь 2026): краткое содержание, технический контекст с пометками о неизвестном, проверка конституции, структура проекта, учёт сложности; рядом — research.md, data-model.md, contracts/, quickstart.md. У каждого выбора стека — обоснование и связь с требованием.
  • Скелет плана дают DDD: контексты → модули, агрегаты → границы согласованности, единый язык → имена, тип поддомена → глубина проработки.
  • Контракт описывает не только форму данных, но и ошибки, гарантии и версии; формы — сигнатуры, JSON Schema, OpenAPI. Исследование записывает варианты и причины отказа от них, ADR — долгоживущие решения с последствиями.
  • Деталей ровно столько, чтобы агенту не пришлось принимать дорогих решений самому: интерфейсы, модель, решения и запреты — да, тела функций — нет.

Дальше: урок SDD.2.3 — как разбить план на маленькие проверяемые задачи с зависимостями и параллельными волнами и что записать в «конституцию» проекта, чтобы не повторять одни и те же правила в каждом промпте.

Закончили урок?Урок пройден

Отметьте его — и 5 карточек из урока попадут в колоду повторения. · 5 карточек — в колоде повторения

Первоисточники 6

  • EN
    GitHub Spec Kit — шаблоны templates/plan-template.md и templates/commands/plan.md
    проверено на октябрь 2026: разделы плана (Summary, Technical Context, Constitution Check, Project Structure, Complexity Tracking), фаза 0 — research.md в формате «решение / обоснование / варианты», фаза 1 — data-model.md, contracts/, quickstart.mdпересказ · MIT
  • EN
    GitHub Spec Kit — Contract-Driven Development (docs/guides)
    что определяет контракт (входы и выходы, ошибки, гарантии вроде идемпотентности, версии), почему одной схемы мало, у контракта один владелецпересказ · MIT
  • EN
    GitHub Spec Kit — Specification-Driven Development (spec-driven.md)
    разделы «Template-Driven Quality» (запрет деталей реализации в спецификации, план без кода, вынос подробностей в отдельные файлы) и «The SDD Workflow in Practice» (каждое решение плана связано с требованием)пересказ · MIT
  • EN
    OpenSpec — Concepts (docs/concepts.md)
    что не писать в спецификацию (имена классов, выбор библиотек) и куда это класть — design.md с разделами «технический подход» и «архитектурные решения»; принцип «облегчённая спецификация по умолчанию»пересказ · MIT
  • EN
    Joel Parker Henderson — Architecture decision record (ADR): шаблоны и советы
    шаблон Майкла Найгарда (статус, контекст, решение, последствия), раздел «Suggestions for writing good ADRs»: одно решение на запись, обоснование, даты, старые записи не переписывают, а заменяютпересказ · CC-BY-NC-SA-4.0
  • EN
    OpenAPI Specification (OpenAPI Initiative)
    README: что такое OpenAPI — машиночитаемое описание HTTP API в YAML или JSON, по которому строят документацию, клиентов и тесты; ссылки на все версии стандартассылка · Apache-2.0

Лицензия текста урока: CC-BY-SA-4.0 · обновлено 2026-10-06