Технический план, модель данных и контракты
Как перейти от спецификации («что и зачем») к техническому плану («как»): архитектура и стек с обоснованием, модель данных, контракты (сигнатуры функций, JSON Schema, OpenAPI), исследование вариантов и записи архитектурных решений. 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
- Фаза 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: скелет плана
Заголовок раздела «Где здесь DDD: скелет плана»Если вы прошли модули 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 (запись на поток) | сущность внутри Cohort | enrollment_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 datetimefrom 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.0info: title: Набор онлайн-школы version: 1.0.0paths: /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)
Заголовок раздела «Исследование вариантов (research)»Когда у вопроса несколько разумных ответов, план не выбирает «на глаз». Исследование
вариантовисследование вариантов (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 проверяет человек.
Записи архитектурных решений (ADR)
Заголовок раздела «Записи архитектурных решений (ADR)»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.md | ADR | |
|---|---|---|
| Живёт | в папке фичи | в журнале решений проекта |
| Отвечает на вопрос | что выбрать для этой фичи | почему система устроена так |
| Формат | вопрос, решение, обоснование, варианты | статус, контекст, решение, последствия |
| Меняется | вместе с фичей | никогда: устаревшую запись заменяют новой |
Последняя строка 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.pytests/acceptance/: test_enrollment.py (по одному тесту на сценарий)
## Риски- Поток могут перенести на другую дату — в спецификации это [УТОЧНИТЬ], вне рамок; пока срок 7 суток считается от текущего starts_at потокаПлан помещается на экран, но решения в нём уже не оставляют агенту места для догадок: контексты, имена, хранение, время, деньги, источники истины для модели и контрактов. Всё подробное — в соседних файлах, на которые план ссылается.
Контракт можно проверить кодом
Заголовок раздела «Контракт можно проверить кодом»Контракт полезен, только если реализация ему следует. Часть проверки автоматизируется в несколько строк: модуль
inspect из стандартной библиотеки умеет читать сигнатуры функций. Сравним черновик агента с протоколом
EnrollmentService:
from __future__ import annotations # аннотации не вычисляются: класс Money для проверки не нужен
import inspectfrom datetime import datetimefrom 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-описанием.
Упражнения (без автопроверки, 30–40 минут)
Заголовок раздела «Упражнения (без автопроверки, 30–40 минут)»- Допишите в
research.mdвопрос R-2 «Где хранить потоки в первой итерации»: решение, обоснование и не меньше двух отвергнутых вариантов. - Напишите ADR-0002 «Деньги — сумма в копейках целым числом и валюта». В последствиях укажите хотя бы один минус.
- Отдайте агенту спецификацию из урока SDD.2.1 и план выше с просьбой: «Не пиши код. Перечисли решения, которые тебе пришлось бы принять самому, потому что их нет в плане». Каждый пункт ответа — либо строка в плане, либо осознанное «оставляем агенту».
Проверь себя
Заголовок раздела «Проверь себя»- Спецификация отвечает на вопросы «что и зачем», технический план — на вопрос «как». Проверка: если реализацию можно поменять, не меняя видимого поведения, — это в план, а не в спецификацию.
- План по шаблону Spec Kit (на октябрь 2026): краткое содержание, технический контекст с пометками о неизвестном,
проверка конституции, структура проекта, учёт сложности; рядом —
research.md,data-model.md,contracts/,quickstart.md. У каждого выбора стека — обоснование и связь с требованием. - Скелет плана дают DDD: контексты → модули, агрегаты → границы согласованности, единый язык → имена, тип поддомена → глубина проработки.
- Контракт описывает не только форму данных, но и ошибки, гарантии и версии; формы — сигнатуры, JSON Schema, OpenAPI. Исследование записывает варианты и причины отказа от них, ADR — долгоживущие решения с последствиями.
- Деталей ровно столько, чтобы агенту не пришлось принимать дорогих решений самому: интерфейсы, модель, решения и запреты — да, тела функций — нет.
Дальше: урок SDD.2.3 — как разбить план на маленькие проверяемые задачи с зависимостями и параллельными волнами и что записать в «конституцию» проекта, чтобы не повторять одни и те же правила в каждом промпте.