Проблема: тесты зелёные, задача не та

AI-агент открыл pull request. Изменения выглядят аккуратно. Тесты зелёные. На ревью выясняется, что агент выбрал другую трактовку правила: клиент получает ответ, второй заказ при этом всё равно появляется. Код и тест согласованы друг с другом. Команда хотела проверить другое поведение.

В этом месте легко обвинить модель, промпт или ревьюера. Проблема возникает раньше: агент получил задачу без заранее согласованного ответа на вопрос «какой наблюдаемый результат должен доказать тест?».

В All Smoke, No Alarm исследователи изучили 86 156 изменений тестовых файлов в 33 596 pull requests, созданных кодинг-агентами в 2 807 GitHub-репозиториях. В 80,2% проанализированных изменений авторы не нашли сильного явного сигнала проверки ожидаемого результата. Это наблюдение для конкретной выборки, пяти агентов и классификации по форме изменений, а не по смыслу теста; оно не измеряет долю «плохих тестов всех AI-агентов». Риск при этом понятен: агент способен написать тест под собственную трактовку задачи и успешно его пройти.

TL;DR

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

Агент реализует задачу и связывает сценарий с конкретным тестом и запуском CI. На ревью команда проверяет путь требование → сценарий → тест → результат CI → решение владельца.

Этот артефакт занимает один файл рядом с задачей. Он не заменяет контракт делегирования: контракт ограничивает полномочия агента, карта приёмки фиксирует смысл результата.

Решение: карта приёмки для одной задачи

Назовём артефакт картой приёмки. Для заметного изменения поведения она хранит пять звеньев:

Требование
  → сценарий приёмки
    → тест или ручная проверка
      → результат CI
        → решение владельца

Карта отвечает на один практический вопрос: «Почему именно этот зелёный тест подтверждает нужное изменение?»

Минимальный шаблон:

requirement:
  id: R-1
  result: Что должно измениться для пользователя или операции
  owner: Кто подтверждает смысл изменения

scenario:
  id: AT-1
  given: Что уже произошло
  when: Какое действие выполняется
  then: Что наблюдает пользователь или система
  rejects:
    - Какой правдоподобный неверный результат должен быть пойман

verification:
  id: VT-1
  covers: AT-1
  test: Путь к тесту или описание ручной проверки
  base_commit_result: Что произошло на исходной версии
  candidate_result: Что произошло на версии с изменением
  ci_evidence: Ссылка на запуск CI
  reviewer: Кто принимает доказательства

Четыре поля сценария и две связи вне его важнее красивой YAML-разметки.

  • Given / when / then удерживают разговор на наблюдаемом поведении.
  • Rejects заставляет назвать ошибку, которую тест обязан заметить.
  • Владелец требования не оставляет смысл изменения на усмотрение автора кода.
  • Связь с CI позволяет открыть фактический запуск проверки, а не читать пересказ в PR.

Разбор решения на одной задаче

Возьмём ограниченную задачу ORD-184: исправить обработку повторного запроса заказа и добавить интеграционный тест, который доказывает нужное поведение.

Платёжный сервис создаёт заказ по запросу клиента. Сеть может оборваться после ответа, и клиент повторит тот же запрос с тем же ключом идемпотентности. Система обязана вернуть исходный заказ. Второй заказ и второе списание появиться не должны.

Агент получает доступ к модулю заказов и тестов, работает в ветке и открывает PR. Он не меняет схему базы данных, публичный API, правила расчёта или настройки в рабочей среде. Если спецификация и код дают разные ответы, агент останавливается и передаёт вопрос владельцу. Эти границы задаёт контракт делегирования.

Шаг 1. До кода владелец утверждает сценарий

Владелец смысла — product owner, domain owner или Tech Lead, в зависимости от устройства команды — подтверждает такую запись:

requirement:
  id: R-1
  change: ORD-184
  result: Повторный запрос с тем же idempotency key не создаёт второй заказ
  owner: Технический владелец сервиса заказов

scenario:
  id: AT-1
  given: Первый валидный запрос уже создал заказ и провёл списание
  when: Клиент повторяет тот же запрос с тем же idempotency key
  then: API возвращает исходный заказ; второго заказа и второго списания нет
  rejects:
    - Создаётся второй заказ
    - API отвечает успешно, хотя появляется второй платёж

Вопрос на этом шаге один: если этот сценарий пройдёт, можно считать нужный результат достигнутым?

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

Шаг 2. Агент связывает сценарий с тестом

Агент добавляет или обновляет интеграционный тест и записывает связь:

verification:
  id: VT-1
  covers: AT-1
  test: tests/orders/idempotency.integration.spec.ts
  base_commit_result: fails
  candidate_result: passes
  ci_evidence: https://ci.example/runs/123

Проверка запускается на исходной версии и падает по ожидаемой причине: система ещё не подтверждает зафиксированный сценарий. На версии с изменением тест проходит. Это даёт ревьюеру сильный сигнал: тест связан с конкретным поведением, а не просто оказался зелёным рядом с новым кодом.

У правила есть рабочие исключения. Тест может закрывать существующее поведение после рефакторинга. Среда может быть недетерминированной. В таких случаях в карте остаётся короткое объяснение, почему шаг с падением на исходной версии неприменим.

Шаг 3. Ревьюер проверяет доказательство, а не пересказ агента

В PR ревьюер открывает карту, тест и запуск CI. Проверка занимает несколько вопросов:

  • Сценарий описывает результат для клиента или системы, а не вызов внутреннего метода?
  • Тест действительно покрывает AT-1?
  • Сценарий поймает второй заказ и второе списание, или проверяется только успешный HTTP-ответ?
  • Агент не поменял сценарий после того, как выбрал реализацию?
  • Запуск CI относится к этому набору изменений?

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

Шаг 4. Владелец принимает результат

Владелец видит исходное требование, сценарий, ссылку на тест, фактический результат CI и PR. Его решение больше не строится на фразе «агент всё проверил».

В этом примере он принимает один конкретный факт: при повторном валидном запросе с тем же ключом система возвращает исходный заказ и не создаёт второй заказ или второе списание.

Почему одного зелёного CI недостаточно

Зелёный CI отвечает на технический вопрос: прошёл ли код тот набор проверок, который запустили.

Карта приёмки добавляет три ответа, которых в CI обычно нет:

  1. Кто подтвердил, что именно это поведение нужно.
  2. Какой неверный результат сценарий обязан отвергнуть.
  3. Где увидеть связь между утверждённым сценарием, тестом и запуском проверки.

SWE-ABS усилил набор тестов в SWE-Bench Verified дополнительными проверками и правдоподобными неверными вариантами реализации. Усиленный набор отклонил 2 184 из 11 041 патчей, которые проходили исходные тесты. Это бенчмарк, не статистика рабочих систем. Практический вывод остаётся полезным: прохождение исходного набора тестов не закрывает вопрос о качестве изменения.

Где хранить карту

Для одной задачи достаточно файла рядом с контрактом и кодом:

.agent/
  contracts/ORD-184.json
  acceptance/ORD-184.yml

Команда, которая использует OpenSpec, хранит требования и сценарии в OpenSpec. Рядом с изменением остаётся только краткая связь сценарий → тест → результат CI и отметка согласования. Так требования не размножаются в двух источниках.

SKA Observatory в стратегии тестирования описывает похожую дисциплину для функциональности и возможностей системы: product owner и команда определяют тесты приёмки, отдельная функция проверяет план тестирования, непокрытые риски получают явное объяснение. Команде не нужно копировать эту систему целиком. Достаточно сохранить её полезную часть: значимый сценарий приёмки становится общим решением до реализации.

В ORD-184 владелец сервиса заказов видит ровно то, что нужно для решения: утверждённый сценарий повторного запроса, тест, запуск CI и PR. Этого достаточно, чтобы принять изменение или вернуть агенту конкретный вопрос.