Тема: Прикладная часть 2. Диагностика дефектов спецификации
Уровень сложности: Средний
Расчётное время изучения: 8-12 часов (теория: 3 часа, практика: 5-9 часов)
Предварительные требования: Часть 7 первого тома: отрицательные требования (negative requirements)
Часть 20 первого тома: антипаттерны SDD (SDD antipatterns)
Часть 11 первого тома: знакомство с доменом appointments-api и Hono JSX
Базовое владение YAML/JSON и JSON Schema
Опыт работы с Qwen Code в режиме Plan Mode
Понимание жизненного цикла SDD: Specify → Plan → Tasks → Implement
Цели обучения: Самостоятельно вносить ровно один контролируемый дефект в спецификацию триажа инцидентов, классифицируя его как cycle, priority_conflict или hidden_out_of_scope
Диагностировать точку застревания Qwen Code через измеримые метрики ask_storm, stage_regress и phase_context_loss с фиксацией в validation.md
Формулировать проверяемое правило-исключение (override), разрешающее конфликт приоритетов, и переводить его в Given/When/Then + JSON Schema
Выполнять обратный прогон полного SDD-контура Specify → Plan → Tasks → Implement для проверки устойчивости исправления
Создавать poisoned/fixed-пару спецификаций с одной строкой восстановления в validation.md, пригодную для переноса в capstone-проект
Обзор: Данный учебный гид посвящён инженерной технике «контролируемо дефектная спецификация» — методике намеренного внесения одного дефекта в спецификацию триажа инцидентов с целью проверки диагностических способностей агента (Qwen Code) и отработки восстановления требований. Техника родственна мутационному тестированию, но применяется на уровне спецификации, а не кода. Ключевой принцип: одна мутация, один симптом, один критерий восстановления. Глава строится на материалах первого тома (отрицательные требования и антипаттерны SDD) и развивает их в направлении управляемого эксперимента: вместо случайных сбоев вы производите предсказуемый, измеримый и исправимый дефект. Учебный кейс — рост latency маршрута appointments-api; зачётный кейс для capstone — high_memory_usage. Результатом работы становится рабочая техника воспроизводимого разрушения и восстановления спецификации как устойчивого контракта.
Ключевые концепции: Контролируемый дефект (controlled defect): Один намеренно внесённый дефект в спецификацию, ограниченный по классу и локализации. Противопоставляется случайной ошибке или накоплению множественных дефектов. Главное правило: одна мутация за итерацию, один ожидаемый симптом, один критерий восстановления. Используется для проверки диагностических способностей агента и отработки техники восстановления.
Ядовитая спецификация (poisoned specification): Учебный ярлык для спецификации с одним контролируемым дефектом. Не следует понимать буквально: «яд» — это не хаос, а строго дозированная мутация. Создаётся как poisoned-spec.md и исправляется в fixed-spec.md с фиксацией в validation.md.
Poisoned/fixed-пара: Пара артефактов: poisoned-spec.md (спецификация с дефектом) и fixed-spec.md (спецификация после исправления). Разница между ними — ровно одна мутация и её патч. Минимальный зачётный набор для capstone включает класс дефекта, строку восстановления и факт отсутствия воспроизведения конфликта.
Классы дефектов — cycle: Циклическая зависимость между состояниями в спецификации триажа. Пример: WAIT_APPROVAL → VALIDATE_ESCALATION → WAIT_APPROVAL. Агент зацикливается, не может выйти из конечного автомата. Проверяется через поиск циклов в графе состояний (find_spec_loops.py).
Классы дефектов — priority conflict: Два правила с одинаковым приоритетом, ведущие к взаимоисключающим действиям. Пример: «эскалировать P0 за 30 секунд» (priority=100) и «любая эскалация требует human approval» (priority=100). Агент не может разрешить, какое правило применить. Проверяется через check_rule_priority.py.
Классы дефектов — hidden out of scope: Действие, которое требование вынуждает выполнить, хотя оно запрещено в constraints. Пример: создание Jira-тикета в приёмочном тесте при запрете Jira в ограничениях. Дефект «спрятан» в семантике, а не в синтаксисе. Требует проверки через JSON Schema и приёмочные тесты.
Метрика ask storm: Диагностический признак: повторные уточняющие запросы агента без появления новых данных. Считается как +1 за каждое новое сообщение, запрашивающее данные, уже названные в предыдущих сообщениях текущей сессии. Сбрасывается при добавлении нового поля в requirements.md или clarifications.md. Эвристика для ручного подсчёта на первом проходе; автоматизируется через парсер транскрипта qwen --output-format json.
Метрика stage regress: Диагностический признак: возврат текущей фазы SDD (specify/plan/tasks/implement) на предыдущую без явной записи причины в validation.md. Считается как +1 за каждый откат. Указывает на неустойчивость спецификации или неразрешённый конфликт в требованиях.
Метрика phase context loss: Диагностический признак: потеря контекста этапа, когда агент в новой фазе ссылается на правило, отсутствующее в текущем requirements.md или plan.md. Булево значение: верно/неверно. Показывает, что спецификация не сохраняет непрерывность между фазами SDD.
Правило-исключение (override): Формальная стратегия разрешения конфликта приоритетов. Определяет, какое требование побеждает на краю времени или критичности. Пример: p0_time_critical_override выше manual_gate_for_noncritical. Должно быть проверяемым через JSON Schema и отражено в приёмочных тестах.
Единственный источник правды: Принцип устранения расхождения между текстом спецификации, JSON Schema и приёмочными тестами. Приоритеты, объявленные в YAML, должны ссылаться на ту же иерархию из тестов и схемы, без параллельной трактовки.
Проверочный инвариант: Фиксация состояния до и после критического перехода. Для эскалации: до — severity, deadline, owner_state; после — channel, audit_record, reason_code. Гарантирует трассируемость и предотвращает формальное «решение» конфликта с потерей контроля.
Обратный прогон sdd-контура: Полная проверка Specify → Plan → Tasks → Implement после исправления дефекта. Необходим для подтверждения, что дефект не переехал из правил в декомпозицию (Tasks) или из декомпозиции в реализацию (Implement). Считается надёжным только повторяемый результат: два последовательных прогона без новых ask_storm, stage_regress и приоритетных конфликтов.
Строка восстановления в validation.md: Формальная запись факта, который перестал воспроизводиться после починки. Пример: priority_conflict=false && escalation_path_resolved=P0 && audit_required=true. Служит критерием готовности и контрольным фактом для ревью.
Практические упражнения: Название: Упражнение 1: Создание poisoned-spec.md с конфликтом приоритетов
Проблема: Возьмите учебный кейс appointment_latency. Создайте файл poisoned-spec.md (или specs/appointment-latency-poisoned.yaml) с двумя конфликтующими правилами: (1) P0 эскалируется за 30 секунд, priority=100; (2) любая эскалация требует human approval, priority=100. Оба правила должны быть формальными (в YAML/JSON), а не в комментарии. Запишите ожидаемый симптом до запуска агента.
Решение: Шаг 1: Скопируйте базовую спецификацию из части 11 первого тома. Шаг 2: Добавьте два правила с одинаковым priority=100, ведущие к взаимоисключающим действиям. Шаг 3: Убедитесь, что конфликт виден в данных: запустите python3 scripts/spec_ci/check_rule_priority.py — должен показать коллизию. Шаг 4: В validation.md запишите ожидаемый симптом: ask_storm >= 4 || stage_regress >= 2 || escalation_path_resolved=false. Шаг 5: Запустите Qwen Code в Plan Mode без изменения файлов, зафиксируйте фактические метрики.
Сложность: beginner
Название: Упражнение 2: Диагностика цикла через find_spec_loops.py
Проблема: Внесите циклическую зависимость в спецификацию: WAIT_APPROVAL → VALIDATE_ESCALATION → WAIT_APPROVAL. Запустите агента и зафиксируйте, как проявляется cycle. Используйте [project script] find_spec_loops.py для визуализации. Сравните поведение агента при cycle и при priority_conflict: чем отличаются метрики ask_storm и stage_regress?
Решение: Шаг 1: В poisoned-spec.md замените линейный переход на циклический: VALIDATE_ESCALATION.when = 'escalation_attempted == true', then = 'goto WAIT_APPROVAL'. Шаг 2: Запустите find_spec_loops.py — получите DOT-граф с обратной дугой. Шаг 3: Запустите агента в Plan Mode, зафиксируйте: при cycle stage_regress доминирует (агент возвращается к той же стадии), при priority_conflict — ask_storm (агент многократно уточняет одно и то же). Шаг 4: В validation.md запишите различие: cycle_count > 0 && stage_regress >= 3 для cycle против ask_storm >= 4 && escalation_path_resolved=false для priority_conflict.
Сложность: intermediate
Название: Упражнение 3: Разрешение конфликта через правило-исключение и JSON Schema
Проблема: Исправьте priority_conflict в appointment_latency через p0_time_critical_override. Правило должно: (а) разрешить автоэскалацию P0 при недоступном владельце; (б) сохранить human_audit_required=true как постфактум; (в) оставить ручной барьер блокирующим для P1-P3. Переведите решение в JSON Schema, запрещающую возврат к скрытому ручному подтверждению.
Решение: Шаг 1: В fixed-spec.yaml создайте p0_time_critical_override: priority=100, when: severity=P0 && owner_unresponsive=true, then: escalation=critical_phone, human_audit_required=true. Шаг 2: Понизьте priority human_gate_noncritical до 10 для P1-P3. Шаг 3: В JSON Schema добавьте условие if-then: если severity=P0 && owner_unresponsive=true, то required: [auto_escalation_channel, human_audit_required, reason_code] с константами critical_phone, true, time_critical_override. Шаг 4: Запустите lint_spec.py — проверьте atomicity. Шаг 5: Запустите check_rule_priority.py — убедитесь, что конфликт исчерпан. Шаг 6: В validation.md запишите строку восстановления: priority_conflict=false && escalation_path_resolved=P0 && audit_required=true.
Сложность: intermediate
Название: Упражнение 4: Полный обратный прогон и перенос в capstone
Проблема: Выполните обратный прогон Specify → Plan → Tasks → Implement для исправленной спецификации. Зафиксируйте, что ни одна фаза не возвращает исходный конфликт. Перенесите результат в capstone/ для кейса high_memory_usage: адаптируйте класс priority_conflict на конфликт между restart_pod и human approval.
Решение: Шаг 1: Запустите Qwen Code последовательно: specify (requirements.md), plan (plan.md), tasks (tasks.md), implement. После каждой фазы фиксируйте метрики в validation.md. Шаг 2: Если Tasks создают несовместимые действия — дефект переехал в декомпозицию, переделайте план. Если Implement проходит, но тесты падают — граница допустимого поведения описана неполно. Шаг 3: Для capstone/high_memory_usage: defect_class=priority_conflict, poisoned: memory_percent>=90 разрешает restart_pod, но любой restart_pod требует human approval с тем же priority. Шаг 4: fixed: restart_pod разрешён как pre-approved action для stateless pod, первый production-запуск требует human_review_for_first_run=true. Шаг 5: validation: priority_conflict=false && action=restart_pod && human_review_for_first_run=true.
Сложность: advanced
Название: Упражнение 5: Скрытый выход за границы (hidden_out_of_scope)
Проблема: Создайте спецификацию, где требование вынуждает создать Jira-тикет, хотя constraints запрещают Jira. Дефект должен быть «скрыт» — синтаксически спецификация корректна, семантически нарушает ограничения. Диагностируйте через phase_context_loss.
Решение: Шаг 1: В constraints.md добавьте forbidden_integrations: [jira]. Шаг 2: В poisoned-spec.md добавьте правило: when: severity=P2, then: create_ticket_in=jira. Шаг 3: Запустите агента — он может формально продолжать, но в фазе Implement сослаться на jira, которой нет в разрешённом списке. Шаг 4: Зафиксируйте phase_context_loss=true: агент в Implement ссылается на правило, отсутствующее в текущих constraints. Шаг 5: Исправьте: замените jira на разрешённый канал (например, internal_tracker) или добавьте исключение в constraints. Шаг 6: Проверьте через JSON Schema: forbidden_integrations должно быть enum с контролируемым списком, а then — reference на этот enum.
Сложность: advanced
Кейсы: Название: Кейс: Рост latency appointments-api и диагностика priority_conflict в production-команде
Сценарий: Платформа онлайн-записи к врачам (домен из части 11 первого тома). Маршрут appointments-api показал рост p95 latency до 2.5 секунд. Команда SRE получила алёрт severity=P0. Спецификация триажа требовала: (1) эскалировать P0 за 30 секунд; (2) любая эскалация требует human approval. Оба правила имели priority=100. Инженер on-call был в самолёте, подтверждение невозможно. Qwen Code, используемый как планировщик инцидентов, застрял в цикле уточнений.
Задача: Агент 12 раз спросил «уточните, доступен ли владелец», хотя статус owner_unresponsive=true был указан в первом сообщении (ask_storm=12). Затем агент вернулся от фазы Plan к Specify, предложив «пересмотреть требования» (stage_regress=2). Эскалация не произошла за 4 минуты, вместо 30 секунд. Команда вручную эскалировала через телефон, потеряв SLA и аудит-след.
Решение: После инцидента команда применила технику контролируемо дефектной спецификации. Создали poisoned-spec.md с воспроизведённым конфликтом. В fixed-spec.md ввели p0_time_critical_override: для P0 при owner_unresponsive=true эскалация разрешена сразу через critical_phone, human_audit_required перенесён в постфактум-аудит. Для P1-P3 оставили ручной барьер. JSON Schema закрепила обязательность полей auto_escalation_channel, human_audit_required, reason_code. Провели обратный прогон: Specify → Plan → Tasks → Implement — метрики ask_storm=0, stage_regress=0, phase_context_loss=false.
Результат: Время реакции на P0 снизилось с 4 минут до 23 секунд. Аудит-след восстановлен: все автоэскалации имеют reason_code=time_critical_override. Спецификация стала повторяемо тестируемой: команда ежемесячно прогоняет poisoned/fixed-пару в учебном режиме. Методика перенесена в capstone-проект high_memory_usage с адаптацией на restart_pod vs human approval.
Извлечённые уроки: Конфликт приоритетов с одинаковым priority=100 не проявляется при нормальной работе, но становится фатальным в стресс-сценарии (owner_unresponsive)
Метрики ask_storm и stage_regress позволяют количественно отличить дефект спецификации от «плохого поведения агента»
Правило-исключение должно менять проверяемое требование, а не только текстовое объяснение — иначе агент вернётся к старой трактовке
Обратный прогон полного контура необходим: локальное исправление в requirements.md может перенести дефект в plan.md или tasks.md
JSON Schema как «единственный источник правды» предотвращает скрытые соглашения через соседние шаги агента
Связанные концепции: priority_conflict
ask_storm
stage_regress
p0_time_critical_override
обратный прогон SDD-контура
единственный источник правды
строка восстановления в validation.md
Название: Кейс: Адаптация техники на high_memory_usage в capstone-проекте
Сценарий: Выпускной проект студента курса SDD. Домен: облачная инфраструктура, алёрт high_memory_usage (memory_percent >= 90 за 10 минут). Студенту требовалось продемонстрировать владение диагностикой дефектов спецификации на новом домене, сохранив класс дефекта из учебного кейса.
Задача: Прямое копирование appointment_latency неприменимо: другие сущности (pod, restart, memory), другие риски (stateful vs stateless, потеря данных при рестарте). Нужно было адаптировать priority_conflict, сохранив структуру: два правила с одинаковым priority, ведущие к взаимоисключающим действиям, и формальное разрешение через override.
Решение: Студент создал poisoned-spec.md: memory_percent>=90 разрешает restart_pod, но любой restart_pod требует human approval, оба с priority=100. В fixed-spec.md ввёл различие stateless/stateful: restart_pod — pre-approved action для stateless pod, для первого production-запуска — human_review_for_first_run=true. Это сохранило аудит, но разблокировало авто действие для известной конфигурации. JSON Schema потребовала поля pod_type, first_run_flag, review_record. Строка восстановления: priority_conflict=false && action=restart_pod && human_review_for_first_run=true.
Результат: Ревьюер подтвердил зачёт: класс дефекта идентифицирован, патч меняет проверяемое правило, обратный прогон чистый. Студент отмечал, что главная сложность — не внесение дефекта, а ограничение ровно одним: первые попытки включали одновременно cycle в графе состояний pod, что сделало диагностику неразличимой.
Извлечённые уроки: Перенос класса дефекта на новый домен требует переосмысления сущностей, но сохранения структуры конфликта
Ограничение «один дефект за итерацию» — самое трудное правило на практике; естественное стремление «усилить» учебный кейс ведёт к неразличимой трассе
human_review_for_first_run=true — пример правила-исключения, которое масштабируется: оно работает и для memory, и для appointment, и для новых доменов
Capstone-ревью ценит не объём артефактов, а точность: ровно один дефект, одна строка восстановления, повторяемый результат
Связанные концепции: poisoned/fixed-пара
priority_conflict
human_review_for_first_run
stateless vs stateful
строка восстановления
capstone-перенос
Советы по изучению: Начинайте с ручного подсчёта метрик (карандаш в validation.md), а не с автоматизации. Понимание эвристики важнее скрипта: вы должны чувствовать, когда ask_storm — это дефект спецификации, а когда — неполнота контекста внешнего мира
Создавайте poisoned-spec.md до запуска агента и записывайте ожидаемый симптом заранее. Это предотвращает постфактум-рационализацию: «ага, он и должен был застрять» — такая логика лишает технику контролируемости
Используйте учебный минимум: appointment_latency для первого прохода, high_memory_usage только для capstone. Не пытайтесь освоить оба домена параллельно — разница в сущностях (latency vs memory, эскалация vs restart) отвлекает от изучения самой техники
Проверяйте дефект через проектные скрипты (find_spec_loops.py, check_rule_priority.py) до запуска Qwen Code. Если скрипт не ловит коллизию — дефект спрятан в комментарий или естественный язык, а не в формальную спецификацию
При обратном прогоне фиксируйте метрики после каждой фазы, а не в конце. Дефект может переехать: Specify чист → Plan чист → Tasks создают несовместимые действия. Показательный признак: stage_regress на границе Plan→Tasks
Практикуйте перевод в Given/When/Then и JSON Schema параллельно, а не последовательно. Они закрывают разные стороны проблемы: GWT — проверяемый сценарий, JSON Schema — запрет недопустимых состояний. Одно без другого оставляет лазейку для агента
Для аудиторного обучения: разделитесь на пары — один создаёт poisoned-spec, другой диагностирует без подсказок. Обмен ролями после разбора. Это имитирует реальную ситуацию, когда автор спецификации «слеп» к своему дефекту
Ведите «мутационный журнал»: записывайте все попытки внесения дефекта, даже неудачные. Частая ошибка — «агент не застрял, хотя должен был». Это не провал, а данные: возможно, дефект был в комментарии, или priority различались, или агент использовал неявное знание. Журнал помогает калибровать интуицию
Не откладывайте метрики ask_storm, stage_regress, phase_context_loss «на потом». На первом проходе они кажутся расплывчатыми, но именно они превращают «странный ответ агента» в диагностический артефакт. Практикуйтесь на коротких сессиях (3-5 сообщений)
Для самопроверки перед зачётом: убедитесь, что diff между poisoned и fixed затрагивает ровно одно проверяемое правило. Если diff — только в комментариях или текстовых пояснениях, исправление не считается
Дополнительные ресурсы: Часть 7 первого тома (отрицательные требования): Базовая теория, на которую опирается диагностика дефектов. Необходима для понимания, почему конфликт приоритетов — это отрицательное требование, вырвавшееся за пределы контроля
Часть 20 первого тома (антипаттерны sdd): Каталог классических ошибок спецификации, служащий источником мутаций. Цикл, priority_conflict, hidden_out_of_scope — производные от этих антипаттернов
Часть 11 первого тома (второй фазовый проект): Исходный домен appointments-api, страница агентов на Hono JSX. Контекст для учебного кейса appointment_latency
Examples/spec-ci/readme.md: Запускаемый аналог базового шлюза спецификации (Spec CI). Проверка формы требований и плана автоматически, близкая к runnable-аналогу из главы
Book2/examples/templates/validation.md: Шаблон формы для записи проверки. Используется для фиксации ожидаемого симптома и строки восстановления
Github spec kit quickstart (https://github.github.io/spec-kit/quickstart.html): Официальное описание фаз Specify → Plan → Tasks → Implement. Ссылка из главы на внешнюю спецификацию SDD-подхода
[project script] find spec loops.py: Скрипт для поиска циклов в графе состояний спецификации. Генерирует DOT-граф для визуализации
[project script] check rule priority.py: Скрипт для проверки коллизий приоритетов в YAML-спецификации. Ловит priority_conflict по числовому совпадению priority
[project script] lint spec.py: Скрипт для проверки атомарности спецификации. Используется при обратном прогоне для валидации fixed-версии
Qwen --output-format json + скрипт-агрегатор: Путь к автоматизации метрик ask_storm, stage_regress, phase_context_loss. На учебном проходе — парсер транскрипта, на продвинутом — CI-метрика
Резюме: Ключевое достижение этой главы — превращение спецификации из набора пожеланий в устойчивый, воспроизводимый контракт. Техника контролируемо дефектной спецификации ( poisoned/fixed-пара ) даёт четыре инструмента: (1) один дефект за итерацию с измеримым симптомом; (2) диагностику застревания через ask_storm, stage_regress, phase_context_loss; (3) формальное разрешение конфликтов через правила-исключения, Given/When/Then и JSON Schema; (4) обратный прогон полного SDD-контура для проверки устойчивости. Учебный минимум — poisoned-spec.md, fixed-spec.md и строка восстановления в validation.md для appointment_latency; capstone-перенос — тот же класс priority_conflict на high_memory_usage. Следующая глава оформит эти правила в constitution.md как первый проектный референдум.