Тема: Прикладная часть 7. Specification CI: спецификация как исполняемый артефакт
Уровень сложности: Средний
Расчётное время изучения: 4-6 часов (теория + практика с runnable-примерами)
Предварительные требования: Базовое понимание Git и GitHub Actions
Опыт работы с Markdown-документами требований (requirements.md, plan.md)
Знакомство с JSON Schema
Прохождение части 9 первого тома (валидация фактов) и части 16 (командное ревью)
Базовый Python для запуска скриптов проверки
Цели обучения: Настроить локальный шлюз спецификации (Spec CI), который блокирует слияние при нарушениях покрытия, области охвата или схемы данных
Построить воспроизводимый граф трассировки requirements → plan с идентификаторами REQ-* и полем implements
Сформулировать диагностические сообщения CI, содержащие файл, строку, идентификатор правила и конкретное действие для исправления
Интегрировать spec_gate в защиту ветки GitHub, чтобы зелёные модульные тесты не могли обойти смысловую проверку
Создать отрицательные фикстуры для JSON Schema и объяснить, почему их предсказуемый отказ защищает контракт
Обзор: Эта глава превращает спецификацию из статичного текста в исполняемый арбитр репозитория. Шлюз спецификации (Spec CI) — это контур GitHub Actions, который на каждый push и pull_request проверяет три слоя: покрытие требований планом (coverage), соответствие плана доменной модели (scope) и корректность примеров данных (schema). Ключевой принцип: спор о качестве спецификации сводится к конкретной строке, правилу и действию. Команда получает воспроизводимый механизм блокировки, а ревьюер проверяет смысл правки, а не расследует логи CI. Глава начинается с локального runnable-примера на incident payload, а затем масштабируется до production-контура с branch protection.
Ключевые концепции: Spec ci (шлюз спецификации): Контур непрерывной интеграции, который проверяет requirements.md, plan.md, validation.md и API-контракты как исполняемые артефакты. Блокирует слияние при трёх классах нарушений: невыполнение требований, выход за границы (out-of-scope), ошибки JSON Schema. Главный термин первого прохода; остальные термины вводятся по мере появления в скриптах.
Gate (шлюз): Обязательная проверка в CI, без прохождения которой слияние невозможно. В отличие от обычных тестов, spec_gate проверяет смысловую целостность, а не только корректность кода. Должен быть помечен как обязательный в настройках защиты ветки.
Coverage-check (проверка покрытия): Построение графа трассировки requirements → plan через стабильные идентификаторы REQ-* и поле implements. Каждая пользовательская история должна иметь реализующую задачу, каждая задача — обратную ссылку. Ошибки: orphan requirement (требование без задачи) и rogue task (задача без требования).
Scope-check (проверка области охвата): Детектор выхода за границы доменной модели. Проверяет, что действия в plan.md разрешены в incident-response domain: acknowledge, escalate, annotate, rollback, notify_on_call. Учитывает актёра, endpoint и условие запуска. Блокирует автономные операции вроде force_resolve_without_operator.
Schema-check (проверка схемы): Валидация JSON-фикстур из validation.md против JSON Schema. Два направления: валидные примеры проходят, отрицательные падают предсказуемо. Если отрицательная фикстура проходит — схема слишком мягкая.
Fixture (фикстура): Пример входных данных, извлекаемый из validation.md для проверки контракта. Валидные фикстуры подтверждают корректную работу, отрицательные — защищают от регрессий схемы.
Spec gate (итоговая задача ci): Завершающий job в workflow, который требует успешного выполнения coverage, scope и schema. Именно эта задача должна быть помечена обязательной в branch protection, иначе зелёные модульные тесты обойдут смысловую проверку.
Json-диагностика: Формат отклонений CI, рассчитанный на быстрое исправление без расследования. Четыре обязательных элемента: понятная причина, ссылка на файл и строку, идентификатор правила, конкретное действие.
Runnable-пример: Локальный учебный кейс на incident payload, который можно запустить без GitHub Actions. Содержит check_coverage.py и validate_schema.py для демонстрации принципов шлюза перед внедрением полноценного CI.
Практические упражнения: Название: Локальный прогон шлюза покрытия
Проблема: Перейдите в директорию book2/examples/spec-ci и запустите скрипт проверки покрытия. Затем намеренно нарушьте связь: добавьте в requirements.md историю REQ-999 без реализующей задачи в plan.md. Запустите скрипт снова и проанализируйте диагностику.
Решение: 1. cd book2/examples/spec-ci
- python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md → ожидается код 0, сообщение coverage ok
- Добавьте в requirements.md: '## REQ-999: Как админ, я хочу автоудаление инцидентов'
- Запустите скрипт снова → ожидается fail с сообщением: 'requirements.md:42: REQ-999 не имеет ссылки в plan.md. Добавьте в plan.md задачу с implements: REQ-999 или удалите требование.'
- Исправьте, добавив в plan.md задачу с implements: [REQ-999], или удалите REQ-999
Сложность: beginner
Название: Отрицательная фикстура и защита схемы
Проблема: В директории book2/examples/spec-ci запустите validate_schema.py. Затем создайте новую отрицательную фикстуру: valid-формат, но с severity: 'P0' и без backup_verified. Проверьте, что схема отклоняет её. Если проходит — схема слишком мягкая.
Решение: 1. python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures → код 0, valid-incident.json: valid, invalid-missing-incident-id.json: expected invalid
- Создайте fixtures/invalid-p0-no-backup.json с _expected_invalid: true, incident_id: 'INC-003', severity: 'P0', но без backup_verified
- Запустите validate_schema.py снова
- Если получено 'invalid-p0-no-backup.json: expected invalid, rejected: missing required property backup_verified' → схема корректна
- Если проходит валидацию → модифицируйте схему: добавьте 'if severity == P0 then backup_verified required' и повторите
Сложность: intermediate
Название: Перенос Spec CI в capstone для high_memory_usage
Проблема: В capstone-проекте создайте строку Spec CI для сценария high_memory_usage. Требование: REQ-HM-01 «не перезапускать pod без подтверждённого RSS > 90% в течение 5 минут». Докажите связь с plan.md и создайте отрицательную фикстуру без incident_id.
Решение: 1. Убедитесь, что в requirements.md есть REQ-HM-01 с чётким условием (RSS > 90%, 5 минут)
- В plan.md добавьте задачу с implements: [REQ-HM-01]
- Запустите: python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md → ожидается coverage ok
- Создайте фикстуру с high_memory_usage payload, но без incident_id, пометьте _expected_invalid: true
- Запустите validate_schema.py → ожидается rejected по missing required property incident_id
- Запишите в capstone/validation.md:
| Spec CI | check_coverage.py | REQ-HM-01 связано с plan.md | PASS | | Schema negative | validate_schema.py | missing incident_id заблокирован | PASS |
Сложность: intermediate
Название: Проверка области охвата: блокировка rogue-операции
Проблема: В учебном plan.md добавьте шаг 'TASK-ROGUE: автономное закрытие инцидента через POST /incidents/{id}/force-resolve без подтверждения оператора, с implements: [REQ-014]'. Проверьте, что scope-check блокирует эту операцию, хотя coverage остаётся зелёным.
Решение: 1. Добавьте TASK-ROGUE в plan.md с implements: [REQ-014] и endpoint POST /incidents/{id}/force-resolve
- Запустите check_coverage.py → зелёный (формальная связь есть)
- Запустите check_scope.py (или анализ вручную по доменной модели incident-response)
- Ожидается fail: 'plan.md:48 uses force_resolve without domain permission'
- Замените на POST /incidents/{id}/ack или добавьте отдельное требование и правило домена
- Повторите до зелёного статуса
Сложность: advanced
Название: Форматирование исправляемой диагностики
Проблема: Получите 'плохое' сообщение об ошибке: 'Coverage failed: missing REQ'. Преобразуйте его в 'хорошее' по четырём элементам: причина, файл/строка, идентификатор правила, действие. Запишите в JSON-формате.
Решение: Исходное (плохое): 'Coverage failed: missing REQ'
Преобразованное (хорошее): { 'status': 'failed', 'check': 'coverage', 'file': 'requirements.md', 'line': 42, 'rule': 'REQ-COV-014', 'reason': 'REQ-014 не имеет реализующей задачи в плане', 'action': 'Добавьте в plan.md задачу с implements: [REQ-014] или удалите требование из requirements.md' }
Проверка: сообщение позволяет исправить без открытия файлов и чтения логов процесса
Сложность: intermediate
Кейсы: Название: Инцидентный конвейер: как отрицательная фикстура предотвратила ложную эскалацию P0
Сценарий: Команда SRE в финтех-компании автоматизировала обработку алертов Grafana → PagerDuty. В validation.md содержались примеры вебхуков с полями incident_id, severity, source. Схема incident_payload.schema.json требовала backup_verified для severity: 'P0'. Спецификация была подключена к Spec CI с тремя слоями: coverage, scope, schema.
Задача: Разработчик 'оптимизировал' схему, убрав условие backup_verified для P0, считая, что 'всегда есть бэкап'. Через неделю другой разработчик добавил в plan.md шаг автоматического эскалации при P0 без изменения требований. Coverage остался зелёным (implements присутствовал), scope тоже (эскалация в домене). Но отрицательная фикстура invalid-p0-no-backup.json внезапно прошла валидацию — схема стала слишком мягкой.
Решение: Spec CI на уровне schema-check обнаружил, что отрицательная фикстура (помеченная _expected_invalid: true) прошла валидацию. Статус spec_gate стал красным, слияние заблокировано. Диагностика указала: 'validation.md:72 schema mismatch: negative fixture invalid-p0-no-backup.json passed, expected reject by missing backup_verified. Action: restore if-then requirement in incident_payload.schema.json: severity P0 requires backup_verified'. Команда восстановила строгость схемы и добавила явное требование REQ-BACKUP-01 в requirements.md.
Результат: Предотвращена ситуация, при которой инцидент с severity P0 без подтверждённого бэкапа автоматически эскалировал бы на дежурного в 3 часа ночи, хотя восстановление было невозможно. Время реакции на регрессию схемы: минуты вместо часов или дней. Команда зафиксировала правило: любое изменение схемы требует пары фикстур — валидной и отрицательной.
Извлечённые уроки: Отрицательные фикстуры — это регрессионные тесты для самой схемы; их прохождение — сигнал тревоги, а не успеха
Изменение схемы без изменения набора фикстур — классический антипаттерн, который Spec CI должен блокировать
Три слоя проверки (coverage, scope, schema) независимы: зелёный статус одного не компенсирует красный другого
Связанные концепции: schema-check
fixture
spec_gate
JSON-диагностика
Название: Rogue task и автономное закрытие: почему coverage недостаточен
Сценарий: В инцидентном проекте команда внедрила Spec CI с проверкой покрытия. Каждый REQ-* имел implements, каждая задача ссылалась на требование. На ревью это выглядело как идеальная трассировка.
Задача: Продукт-менеджер попросил 'ускорить' разрешение инцидентов. Разработчик добавил в plan.md задачу TASK-AUTO-RESOLVE с implements: [REQ-014] (общее требование 'дежурный получает подтверждение эскалации') и endpoint POST /incidents/{id}/force-resolve. Формально связь есть, но содержание — автономное закрытие без оператора — выходит за рамки доменной модели incident-response. Coverage-check прошёл зелёным.
Решение: Scope-check сработал на уровне доменной модели: действие force_resolve не входит в разрешённые операции (acknowledge, escalate, annotate, rollback, notify_on_call). Проверка учла не только глагол, но и актёра (автономный агент vs дежурный) и endpoint. Диагностика: 'plan.md:48: IR-SCOPE-007 — Autonomous force resolve is outside the incident-response domain model. Action: Replace with POST /incidents/{id}/ack or add an approved requirement and domain rule'.
Результат: Пулл-реквест заблокирован. Команда обсудила и отклонила автономное закрытие как рискованное. Вместо этого добавили явное требование REQ-MANUAL-ACK и задачу с человеческим подтверждением. Доменная модель осталась неизменной, доверие к ремедиации сохранено.
Извлечённые уроки: Граф покрытия по идентификаторам сильнее поиска по словам, но недостаточен без проверки содержания
Scope-check ловит semantic drift: когда формальная структура сохранена, а смысл изменён
Доменная модель — контракт между командой и системой; её нарушение опаснее отсутствия тестов
Связанные концепции: scope-check
coverage-check
rogue task
domain model
Советы по изучению: Начинайте с локального runnable-примера в book2/examples/spec-ci, не с GitHub Actions. Сначала добейтесь зелёного шлюза локально, затем переносите в CI
Используйте pre-commit hook только для изменённых файлов, полный прогон оставляйте для CI — это экономит время и делает шлюз привычной частью цикла
Практикуйтесь на 'плохих' диагностиках: берите сообщения вроде 'Coverage failed' и переписывайте в формат с файлом, строкой, правилом и действием
Создавайте отрицательные фикстуры параллельно с положительными — это защищает от 'мягких' регрессий схемы
Ведите 'дневник нарушений': записывайте, какие ошибки ловил spec_gate, и используйте для обучения команды
Для визуального стиля: нарисуйте на бумаге граф requirements → plan → domain → schema, отметьте, где какой gate срабатывает
Для аудиального стиля: объясните коллеге вслух, почему триггер push в main важен так же, как pull_request (прямое обновление служебных файлов)
Для кинестетического стиля: физически удалите строку implements из plan.md, запустите скрипт, почувствуйте блокировку, затем восстановите
Дополнительные ресурсы: Github spec kit: https://github.com/github/spec-kit — референсная реализация SDD-подхода, где требования, план и задачи становятся проверяемыми слоями
Runnable-примеры главы: book2/examples/spec-ci/scripts/check_coverage.py и validate_schema.py — локальный smoke-test без внешних зависимостей
Json schema validation: https://json-schema.org/understanding-json-schema/ — спецификация для создания строгих контрактов фикстур
Часть 9 первого тома: part-09-feature-validation.md — связь validation.md с фактами, база для понимания фикстур
Часть 16 первого тома: part-16-team-code-review.md — командное ревью пакета доказательств, контекст для автоматизации
Часть 12 первого тома: part-12-mvp.md — REQ-идентификаторы и схемы полезной нагрузки в учебном AgentClinic
Резюме: Шлюз спецификации (Spec CI) превращает requirements.md, plan.md, validation.md и API-контракты из справочной документации в исполняемые артефакты, блокирующие слияние. Три слоя проверки — coverage (граф REQ-* → implements), scope (соответствие доменной модели incident-response), schema (JSON-фикстуры с отрицательными примерами) — независимы и дополняют модульные тесты. Ключевой выигрыш: спор о спецификации сводится к диагностике с файлом, строкой, правилом и действием, снижая нагрузку на ревьюера. Для внедрения: сначала локальный runnable-пример, затем GitHub Actions с обязательным spec_gate в branch protection, всегда на push и pull_request. В инцидентной автоматизации такая строгость предотвращает ложные эскалации, опасные автооперации и потерю доверия к ремедиации.