Материал: Прикладная часть 7. Specification CI: спецификация как исполняемый артефакт

Урок 1 из 5 в модуле «Прикладная часть 7. Specification CI: спецификация как исполняемый артефакт»
Вы просматриваете урок без входа. Войдите, чтобы сохранять прогресс и проходить тесты.

Прикладная часть 7. Specification CI: спецификация как исполняемый артефакт

Статус: Рекомендация. Запускать проверки спецификации в CI — устойчивая практика. Конкретный набор шлюзов (coverage, scope, schema, spec_gate) и формат JSON-диагностики — рекомендуемая рамка, которую большинство команд адаптирует. JSON Schema-проверка фикстур из validation.md — стандартное использование инструмента.

В этой главе «шлюз спецификации» — короткое название контура, который проверяет спецификацию так же, как обычный CI проверяет код. Сама проверка состоит из обычных шагов: парсинг requirements.md и plan.md, валидация примеров против JSON Schema, проверка соответствий между файлами. Все остальные термины — gate, fixture, schema-check, coverage-check — вводятся ниже в тех местах, где они реально появляются в команде или скрипте; в одну вступительную строку их сводить не нужно.

Шлюз спецификации — это автоматизация ровно той процедуры, которую часть 9 первого тома проводила глазами, а часть 16 — командно в пулл-реквесте. В учебном AgentClinic REQ-идентификаторы и схемы полезной нагрузки для отзывов из части 12 ещё могли оставаться договорённостью. В production такого запаса доверия нет. Те же связи требуется превратить в обязательный шлюз, который зелёные модульные тесты не могут обойти.

Перед чтением

  • Опора из первого тома: часть 9 связывает validation.md с фактами, часть 16 показывает ревью пакета доказательств.
  • Локальный учебный кейс: incident payload, потому что coverage и JSON Schema можно проверить локально без CI.
  • След для capstone/: одна строка Spec CI для high_memory_usage: команда, доказанный факт и отрицательный пример.
  • Главный термин первого прохода: Spec CI. gate, fixture, schema-check, coverage-check — справочные, появляются прямо в команде и комментарии скрипта.
  • Что отложить: GitHub Actions workflow, scope-gate и извлечение фикстур из произвольного validation.md.

Цель

В этой главе шлюз спецификации превращается из идеи «проверять документы» в рабочий контур GitHub Actions для инцидентного проекта. Каждый push и каждый пулл-реквест проходят обязательный шлюз. Шлюз блокирует слияние при трёх классах нарушений:

  • невыполнение требований,
  • выход за границы (out-of-scope),
  • ошибки JSON Schema.

Читатель получит практическую схему репозитория, где requirements.md, plan.md, validation.md и API-контракты проверяются как исполняемые артефакты, а не как справочная документация.

Главный выигрыш — команда получает воспроизводимый механизм блокировки в CI. Спор о качестве спецификации сводится к конкретной строке, правилу и действию для исправления.

Минимальный учебный сценарий

Учебный кейс

incident payload: проверить, что requirements.md связан с plan.md, а JSON-фикстуры (примеры входных данных, которые мы достаём из validation.md) содержат обязательный incident_id. Цель — увидеть Spec CI как маленький локальный шлюз (обязательную проверку, без которой слияние блокируется), а не как большой GitHub Actions-процесс.

Подготовка

  • book2/examples/spec-ci/requirements.md.
  • book2/examples/spec-ci/plan.md.
  • book2/examples/spec-ci/fixtures/valid-incident.json.
  • book2/examples/spec-ci/fixtures/invalid-missing-incident-id.json.
  • Скрипты check_coverage.py и validate_schema.py.

Шаги

  1. cd book2/examples/spec-ci. Ожидание: вы в каталоге runnable-примера.
  2. python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md. *Ожидание: код возврата 0, все REQ-* имеют связь с планом.*
  3. python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures. Ожидание: валидная фикстура проходит, отрицательная падает предсказуемо.
  4. Откройте сообщение об ошибке для отрицательной фикстуры. Ожидание: понятно, какое поле отсутствует и какой файл исправлять.
  5. Запишите в validation.md, что именно блокирует шлюз: покрытие, область охвата или схема.

Контрольный факт

Один локальный прогон показывает два типа истины: требование связано с планом, а данные соответствуют контракту. Если ошибка CI не указывает файл, правило и действие, она не готова для команды.

Как это попадает в capstone/

Перенесите в capstone/validation.md одну строку Spec CI: какая команда запускалась, что она доказала, какой отрицательный пример был заблокирован. Не переносите полный GitHub Actions workflow, если он не создан; для учебного минимума достаточно runnable-аналога из examples/spec-ci.

Минимальный фрагмент:

| Spec CI | `python3 scripts/check_coverage.py ...` | все REQ-* связаны с plan | PASS |
| Schema negative | `python3 scripts/validate_schema.py ...` | missing incident_id заблокирован | PASS |

Перенос на high_memory_usage

Учебный пример работает на incident payload, но в capstone/ нужна строка для high_memory_usage. Подставьте те же два класса проверок к своим требованиям:

Что проверяемКоманда (учебная)Что доказывает для high_memory_usage
Coveragecheck_coverage.py --requirements requirements.md --plan plan.mdтребование REQ-HM-01 «не перезапускать pod без подтверждённого RSS > 90% в течение 5 минут» связано с задачей в plan.md

| Schema negative | validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures | фикстура без incident_id или с severity: "P0" без backup_verified блокируется |

Если строки Coverage и Schema negative для high_memory_usage написать нельзя — значит, в requirements.md ещё нет проверяемого требования или схема ещё не различает P0 без бэкапа.

Ревьюируемый след

В учебном пакете сохраняйте изменения в requirements.md, plan.md, validation.md или схеме. Временные фикстуры, созданные только для локальной диагностики, не нужны, если они не стали частью регрессионного набора.

Ключевые идеи

Здесь «исполняемый артефакт» означает не запуск Markdown как программы. Речь о проверке требований, плана и примеров обычными CI-скриптами.

Запускайте обязательный шлюз GitHub Actions и на pull_request, и на push в защищённую ветку. Почему оба триггера. Нарушение спецификации может попасть двумя путями: через обычный пулл-реквест или через прямое обновление служебных файлов.

Минимальный набор отслеживаемых артефактов:

  • requirements.md,
  • plan.md,
  • validation.md,
  • contracts/**,
  • constitution.md — при необходимости, если в ней зафиксированы доменные ограничения инцидентного конвейера.

В настройках защиты ветки (branch protection) пометьте именно итоговую задачу spec_gate как обязательную. Иначе зелёные модульные тесты смогут обойти смысловую проверку. Такая схема соответствует SDD-подходу, где требования, план и задачи становятся проверяемыми слоями, а не статичным текстом (GitHub Spec Kit).

> [project script].github/workflows/spec-ci.yml вызывает проектные скрипты scripts/spec_ci/*.py.

name: spec-ci

on:
  pull_request:
    paths:
      - 'requirements.md'
      - 'plan.md'
      - 'validation.md'
      - 'contracts/**'
      - 'constitution.md'
  push:
    branches: [main]
    paths:
      - 'requirements.md'
      - 'plan.md'
      - 'validation.md'
      - 'contracts/**'
      - 'constitution.md'

jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: python3 scripts/spec_ci/check_coverage.py --requirements requirements.md --plan plan.md --out out/spec-ci/coverage-report.json

  scope:
    runs-on: ubuntu-latest
    needs: [coverage]

steps:
      - uses: actions/checkout@v4
      - run: python3 scripts/spec_ci/check_scope.py --domain models/incident-response.yaml --plan plan.md --contracts contracts/api.md --out out/spec-ci/scope-violations.ndjson

  schema:
    runs-on: ubuntu-latest
    needs: [coverage, scope]
    steps:
      - uses: actions/checkout@v4
      - run: python3 scripts/spec_ci/extract_fixtures.py --from validation.md --out out/spec-ci/fixtures
      - run: python3 scripts/spec_ci/validate_schema.py --schemas schemas --fixtures out/spec-ci/fixtures --out out/spec-ci/schema-audit.json

  spec_gate:
    runs-on: ubuntu-latest
    needs: [coverage, scope, schema]
    steps:
      - run: echo "Specification gate passed"

Проверка покрытия начинается с графа requirements → plan, а не с поиска совпадающих слов. Введём правила трассировки:

  • каждая пользовательская история из requirements.md получает стабильный идентификатор REQ-*;
  • каждая задача или шаг в plan.md обязан ссылаться на один или несколько таких идентификаторов через поле вроде implements: [REQ-014].

Что считать ошибкой. Если история не имеет трассируемой задачи, завершайте процесс с fail: команда уже потеряла обещание пользователю до начала реализации. Обратное нарушение тоже важно. Задача без implements превращается в неприкаянную (rogue task). Это значит, что план начал добавлять функциональность, не подтверждённую требованиями.

> [project script]scripts/spec_ci/check_coverage.py; запускаемый аналог — [examples/spec-ci/scripts/check_coverage.py](examples/spec-ci/).

python3 scripts/spec_ci/check_coverage.py \
  --requirements requirements.md \
  --plan plan.md \
  --out out/spec-ci/coverage-report.json

Детектор выхода за границы нужен для случаев, когда формальная трассировка есть, но содержание шага выходит за пределы инцидентного домена. Сопоставляйте действия из plan.md с доменной моделью incident-response. Допустимые операции, например:

  • acknowledge,
  • escalate,
  • annotate,
  • rollback,
  • notify_on_call.

Произвольные бизнес-действия (вроде notify_finance, close_customer_contract или force_resolve_without_operator) сюда не входят.

Учитывайте не только глагол. Проверяйте также:

  • актёра,
  • конечную точку (endpoint),
  • условие запуска.

Почему так. resolve incident может быть разрешён ручному дежурному оператору и запрещён автономному агенту. Практическое правило простое: если шаг нельзя объяснить через модель инцидента и разрешённый API-контракт, он блокирует пулл-реквест.

> [project script]scripts/spec_ci/check_scope.py. Готового аналога нет; реализуйте сами поверх доменной модели и API-контракта.

python3 scripts/spec_ci/check_scope.py \
  --domain models/incident-response.yaml \
  --plan plan.md \
  --contracts contracts/api.md \
  --out out/spec-ci/scope-violations.ndjson

Проверки JSON Schema закрывают слой фикстур и примеров полезной нагрузки, где часто возникают тихие регрессии интеграций. Что нужно делать:

  • извлекайте из validation.md все JSON-блоки;
  • преобразовывайте их в отдельные фикстуры;
  • валидируйте против схем из schemas/** — например, incident_payload.schema.json, pagerduty_webhook.schema.json или grafana_alert.schema.json.

Следите за двумя направлениями. Валидные примеры должны проходить без ошибок. Специально отрицательные примеры должны падать предсказуемо. Если отрицательная полезная нагрузка проходит, схема слишком мягкая и не защищает контракт.

До слияния в защищённую ветку это проверяется так же строго, как тесты приложения. Неверный incident_id, некорректный severity или пустой source способны сломать весь контур ремедиации.

> [project script]scripts/spec_ci/extract_fixtures.py и scripts/spec_ci/validate_schema.py; запускаемый аналог проверки схемы — [examples/spec-ci/scripts/validate_schema.py](examples/spec-ci/). Шаг извлечения реализуйте сами под формат validation.md вашего проекта.

python3 scripts/spec_ci/extract_fixtures.py \
  --from validation.md \
  --out out/spec-ci/fixtures

python3 scripts/spec_ci/validate_schema.py \
  --schemas schemas \
  --fixtures out/spec-ci/fixtures \
  --out out/spec-ci/schema-audit.json

Делайте диагностический формат CI-отклонений рассчитанным на быстрое исправление, а не на расследование по логам процесса.

Плохо: > Coverage failed: missing REQ

Проблема: ревьюер не знает, какое требование осиротело и куда смотреть. Ошибку нельзя исправить без отдельного расследования.

Хорошо: > requirements.md:42: REQ-014 не имеет ссылки в plan.md. Добавьте в plan.md задачу с implements: REQ-014 или удалите требование.

В каждой ошибке указывайте четыре элемента:

  • понятную причину,
  • ссылку на файл и строку,
  • идентификатор нарушенного правила,
  • конкретное действие для команды спецификации.

Типы ошибок выглядят так. Для покрытия это может быть REQ-021 has no implementing plan item; add implements: [REQ-021] to plan.md or remove the requirement. Для области охвата — plan.md:48 uses force_resolve without domain permission. Для схемы — validation.md:72 missing required property incident_id.

Такой формат снижает нагрузку на ревьюера. Человек проверяет смысл правки, а не восстанавливает, что именно сломал CI.

{
  "status": "failed",
  "check": "scope",
  "file": "plan.md",
  "line": 48,
  "rule": "IR-SCOPE-007",
  "reason": "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"
}

Примеры и применение

flowchart LR
A[pre-commit hook]
B[локальный быстрый прогон и лёгкая дуэль до push]
C[PR push]
D[выделение изменённых файлов]
E[check_coverage требования план задачи граф]
F[check_scope доменная модель и contracts/api]
G[check_schema validation и контрпримеры]
H[отчёт шлюза и статус PR]
A --> B --> C --> D --> E --> F --> G --> H

Типовой пулл-реквест в учебном инцидентном репозитории меняет три файла:

  • requirements.md,
  • plan.md,
  • validation.md.

Автор описывает историю REQ-014: как дежурный (on-call) инженер, я хочу получать подтверждение эскалации. Затем в плане добавляет задачу TASK-033 с implements: [REQ-014]. А в validation.md кладёт пример полезной нагрузки вебхука с полями incident_id, severity, source и escalation_target.

Что проверяется. Проверка покрытия проходит, если связь REQ-014 → TASK-033 существует. Проверка области охвата проходит, если действие соответствует доменной модели. Проверка схемы проходит, если полезная нагрузка совпадает с контрактом. Если любой из трёх слоёв ломается, spec_gate возвращает красный статус и GitHub не разрешает слияние.

Показательный сбой: автор пытается «ускорить» обработку и добавляет в plan.md шаг POST /pagerduty/force-resolve без отдельного требования и без разрешения в доменной модели. Покрытие может остаться зелёным, если шаг формально привязан к существующей истории. Но проверка области охвата заблокирует пулл-реквест: автономное закрытие инцидента без подтверждения дежурного не входит в согласованные операции.

Если тот же пулл-реквест добавляет в validation.md полезную нагрузку с event_code вместо обязательного incident_id, проверка схемы выдаёт независимый блокер. Команда получает два разных класса ошибок:

  • смысловой выход за границы,
  • нарушение структуры данных.

Локальный быстрый прогон перед push экономит время и делает шлюз спецификации привычной частью рабочего цикла. В pre-commit запускайте только изменённые файлы. Полный процесс оставляйте GitHub Actions, чтобы не тормозить разработчика длинной проверкой всех фикстур.

Для инцидентного проекта достаточно команды, которая делает три вещи:

  • строит граф покрытия,
  • проверяет область охвата по различиям (diff),
  • валидирует затронутые JSON-блоки.

Если локальный отчёт уже показывает orphan requirement, rogue task или schema mismatch, автор исправляет спецификацию до создания пулл-реквеста, а не после получения красного статуса в удалённом CI.

> [project script] — пример локальной обёртки для scripts/spec_ci/*.py.

#!/usr/bin/env bash
set -euo pipefail

python3 scripts/spec_ci/check_coverage.py \
  --requirements requirements.md \
  --plan plan.md \
  --out out/spec-ci/coverage-report.json

python3 scripts/spec_ci/check_scope.py \
  --domain models/incident-response.yaml \
  --plan plan.md \
  --contracts contracts/api.md \
  --out out/spec-ci/scope-violations.ndjson

python3 scripts/spec_ci/extract_fixtures.py \
  --from validation.md \
  --out out/spec-ci/fixtures

python3 scripts/spec_ci/validate_schema.py \
  --schemas schemas \
  --fixtures out/spec-ci/fixtures \
  --out out/spec-ci/schema-audit.json

Итог

Шлюз спецификации делает спецификацию исполняемым арбитром репозитория. GitHub Actions блокирует пулл-реквест при трёх классах нарушений:

  • непокрытые пользовательские истории,
  • посторонние сценарии в плане,
  • ошибки JSON Schema в валидационных примерах.

Для команды это меняет характер ревью. Вместо субъективного спора о полноте требований появляется диагностический отчёт с файлом, строкой, правилом и действием.

В инцидентной автоматизации такая строгость особенно важна. Неверная область охвата или слабый контракт полезной нагрузки могут привести к трём последствиям:

  • ложные эскалации,
  • опасные автооперации,
  • потеря доверия к ремедиации.

Далее этот контур станет основой для файлового арбитража спорных изменений.

Минимальный запускаемый набор для этой главы лежит в examples/spec-ci/. Пройдите его до внедрения полноценного процесса GitHub Actions. Сначала добейтесь зелёного локального шлюза. Затем переносите те же команды в CI.

> [runnable] — запускаемый пример: examples/spec-ci/scripts/check_coverage.py и examples/spec-ci/scripts/validate_schema.py.

cd book2/examples/spec-ci
python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md
python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures

Артефакты и критерии готовности

АртефактГотов, когда

| Локальный прогон book2/examples/spec-ci | smoke-pass без внешних зависимостей | | Проверка покрытия requirements → plan | каждая REQ-* имеет реализующую задачу, каждая задача имеет implements | | Проверка JSON Schema | валидная фикстура проходит, отрицательная падает предсказуемо | | Запись в validation.md | сообщение об ошибке шлюза указывает файл, правило и действие для исправления |

Полный трек добавляет .github/workflows/spec-ci.yml или его проектный аналог, out/spec-ci/coverage-report.json для графа requirements → plan, out/spec-ci/scope-violations.ndjson с нарушениями доменной модели, out/spec-ci/schema-audit.json по фикстурам из validation.md и локальную обёртку быстрого прогона. Считайте его готовым, если проверка области охвата блокирует автономные действия вне модели incident-response, задача spec_gate обязательна в защищённой ветке, а диагностический формат CI указывает файл, строку, идентификатор правила и действие.

Практика

  1. cd book2/examples/spec-ci && python3 scripts/check_coverage.py --requirements requirements.md --plan plan.md — *ожидание: код 0, stdout — одна строка coverage ok: 3 requirements covered.*
  1. python3 scripts/validate_schema.py --schema schemas/incident_payload.schema.json --fixtures fixtures — *ожидание: код 0; stdout содержит valid-incident.json: valid и invalid-missing-incident-id.json: expected invalid, rejected: missing required property incident_id (отрицательная фикстура помечена _expected_invalid: true и поэтому считается успешно отклонённой).*
  2. Перенесите в capstone/validation.md одну строку Spec CI: «coverage ok: 3/3, schema ok: 2/2 (отрицательная отклонена по missing required property incident_id)». Ожидание: при следующем регрессе строка позволяет восстановить, что именно блокирует слияние, без чтения логов CI.

Контрольные вопросы

  1. Почему покрытие по словам слабее графа requirements → plan?
  2. Какие нарушения должна ловить проверка области охвата, а какие — нет?
  3. Что делает CI-ошибку исправляемой без расследования?
  4. Шлюз спецификации блокирует слияние из-за несовпадения REQ-ID. Программист хочет добавить REQ-ID к существующему пункту плана и слить пулл-реквест. Что в этом подходе опасно?
Мои заметки
0 / 10000

Заметки сохраняются в этом браузере. На другом устройстве они не появятся.

Меню курса

Курс

Production SDD для Qwen Code CLI. Часть 2
Прогресс 0 / 100