Files

8 lines
22 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 106,
"slug": "editorial-2025-01-field-ai-coding-assistant",
"title": "AI-помощник в задаче: как проверить diff перед merge",
"excerpt": "Сгенерированный код может пройти happy path и всё равно выйти за границу задачи, изменить контракт или добавить непроверенную зависимость. Разбираем практический gate перед merge: scope, отрицательные ветки, evidence и ограничения человеческого решения.",
"contentHtml": "<p>На ревью приходит небольшой diff, который подготовил AI-помощник. Основной тест зелёный, названия аккуратные, объяснение звучит уверенно. При чтении полного изменения обнаруживается второй файл с правами доступа, новый пакет в lock-файле или ветка ошибки, в которой запись выполняется до проверки входа. Такой diff выглядит локальным только в окне редактора. Цена пропуска — сломанный контракт, утечка данных или откат, который придётся готовить уже после merge.</p>\n<p>Разберём учебный сценарий: помощник должен нормализовать входящее событие, но предложил изменить ещё и вызывающий код. Главный вывод не зависит от конкретного инструмента: ответ модели — кандидат на изменение, а не доказательство корректности. Перед merge человек должен связать каждый изменённый путь с задачей, каждую изменённую ветку — с контрактом, а каждое существенное утверждение — с воспроизводимой проверкой.</p>\n<h2>Что считается готовым diff</h2>\n<p>Готовность здесь означает не «код выглядит правильно», а возможность показать короткую цепочку evidence. Сначала есть формулировка задачи и список разрешённых областей. Затем — diff, который не вышел за эти области. Для изменённого поведения записаны вход, ожидаемый результат и запрещённый побочный эффект. Наконец, тест или другой запуск действительно проходит через эту ветку в окружении, которое указано в результате проверки.</p>\n<p>У gate есть три исхода. <code>approve</code> означает, что reviewer готов принять конкретный diff. <code>request-changes</code> означает, что проблему можно исправить в той же задаче. <code>stop</code> означает, что обнаружена неизвестная граница, security-sensitive изменение или несоответствие контракта; такой случай сначала возвращают владельцу риска.</p>\n<figure><img src=\"/assets/editorial/2025/ai-coding-assistant-2025-verification-gate.svg\" alt=\"Схема проверки сгенерированного diff: карточка задачи, ограниченный diff, строки контракта и целевой тест сходятся в gate; несоответствие ведёт к остановке, а решение принимает человек\" loading=\"lazy\" /><figcaption>Gate не оценивает убедительность ответа модели. Он сопоставляет scope, контракт и evidence. Красная ветка означает остановку до merge; схема не запускает CI и не доказывает безопасность production-системы.</figcaption></figure>\n<h2>Сначала проверьте границу изменения</h2>\n<p>Первый вопрос к diff — не «хорошо ли написан код», а «имеет ли он право здесь находиться». Для pull request можно получить список изменённых путей и отдельную проверку пробелов или конфликтных маркеров:</p>\n<pre><code>git diff --name-status origin/main...HEAD\ngit diff --check origin/main...HEAD\ngit diff --stat origin/main...HEAD</code></pre>\n<p>Эти команды показывают состав изменения и базовые технические дефекты, но не знают смысла задачи. Сопоставьте каждый путь с карточкой: исходный модуль, тест, схема, миграция, lock-файл и конфигурация — это разные виды риска. Если помощник добавил пакет «для удобства», изменил генератор или затронул авторизацию, не прячьте это в общий diff. Уточните границу либо вынесите изменение в отдельную задачу с отдельным владельцем.</p>\n<p>Особенно легко пропустить сгенерированные файлы. Они могут появиться после команды сборки, хотя не были частью замысла. Проверьте статус репозитория до и после запуска инструментов, а также правила, по которым артефакты попадают в commit. Для rename и удаления смотрите не только имя нового файла: контракт мог переехать, а тест — остаться привязанным к старому пути.</p>\n<h2>Затем разложите контракт по веткам</h2>\n<p>Небольшая функция часто имеет больше одного смысла входа. В учебном обработчике события есть валидное сообщение, отсутствие обязательного поля и неизвестный статус. Если все три случая свести к пустому объекту, вызывающий код может принять ошибку за отсутствие данных и продолжить обработку. Это не косметическая разница: меняется решение на границе системы.</p>\n<div class=\"table-scroll\"><table><caption>Минимальная матрица проверки для обработчика события</caption><thead><tr><th scope=\"col\">Вход</th><th scope=\"col\">Ожидаемый результат</th><th scope=\"col\">Запрещённый эффект</th><th scope=\"col\">Evidence</th></tr></thead><tbody><tr><td>Поле <code>type</code> известно, payload полный</td><td>Нормализованный объект передан дальше</td><td>Не создавать запись дважды</td><td>Unit test и проверка вызова downstream</td></tr><tr><td>Нет <code>type</code></td><td><code>invalid-input</code></td><td>Не вызывать запись и повторную доставку</td><td>Negative test с проверкой числа вызовов</td></tr><tr><td><code>type</code> неизвестен</td><td><code>unsupported-type</code></td><td>Не угадывать обработчик по похожему имени</td><td>Тест на неизвестное значение и лог причины</td></tr><tr><td>Payload содержит лишнее поле</td><td>Результат зависит от версии контракта</td><td>Не молча терять данные, если контракт их запрещает</td><td>Сверка схемы и тест сериализации</td></tr></tbody></table></div>\n<p>Таблица не заменяет спецификацию API. Она делает видимым место, где тест должен отличать результат от побочного эффекта. Проектные значения — имена статусов, политика лишних полей и способ дедупликации — нельзя переносить в другой сервис без сверки его контракта.</p>\n<h2>Положительный тест без отрицательного пути недостаточен</h2>\n<p>Минимальный воспроизводимый пример можно выполнить обычным Node.js без внешних пакетов. Он проверяет три входа и отдельно фиксирует, что downstream не вызывается при ошибке:</p>\n<pre><code>const calls = [];\n\nfunction normalizeEvent(event) {\n if (!event?.type) return { status: 'invalid-input' };\n if (event.type !== 'invoice.created') return { status: 'unsupported-type' };\n return { status: 'ok', type: event.type, invoiceId: event.invoiceId };\n}\n\nfunction handle(event) {\n const result = normalizeEvent(event);\n if (result.status !== 'ok') return result;\n calls.push(result.invoiceId); // downstream: только после проверки\n return result;\n}\n\nconsole.log(handle({ type: 'invoice.created', invoiceId: 'inv-7' }));\nconsole.log(handle({ invoiceId: 'inv-8' }));\nconsole.log(handle({ type: 'invoice.deleted', invoiceId: 'inv-9' }));\nconsole.log({ calls });\n\n// Ожидается: два отказа и calls только с ['inv-7'].</code></pre>\n<p>Запустите этот фрагмент как <code>node example.mjs</code> или вставьте его в Node REPL. Важно не само число строк, а наблюдаемый инвариант: ошибки возвращаются до побочного действия. Если сгенерированный вариант вызывает <code>calls.push</code> до проверки статуса, happy path останется зелёным, а отрицательный тест покажет нарушение.</p>\n<p>Тест должен быть связан с изменённой веткой, а не просто лежать рядом по имени. Прочитайте assertion: он проверяет значение, тип ошибки, количество вызовов и состояние после отказа? Если проверяется только непустой ответ, неизвестно, что произойдёт с записью, повторной доставкой или метрикой.</p>\n<h2>Проверьте зависимости и границы данных</h2>\n<p>AI-помощник может предложить несуществующий пакет, устаревший API или библиотеку с неподходящей лицензией. Может также перенести в prompt секрет, персональный идентификатор или фрагмент закрытого кода. Поэтому просмотр diff должен включать не только строки программы.</p>\n<ol><li><strong>Зависимости.</strong> Сверьте имя пакета с реестром и документацией, версию — с поддерживаемым runtime, а лицензию и транзитивные зависимости — с правилами проекта.</li><li><strong>Секреты.</strong> Уберите токены, cookies, персональные данные и закрытый исходный код из контекста помощника. Проверьте diff, логи и артефакты на случайное попадание таких данных.</li><li><strong>Права.</strong> Если изменение касается авторизации, фильтрации данных, SQL, файловой системы или внешнего вызова, назначьте ревьюера с соответствующей ответственностью. Статический анализ не заменяет такой review.</li><li><strong>Схема и сериализация.</strong> Сравните фактический JSON, заголовки, коды ошибок и обязательность полей с владельцем API. Компилируемый тип не доказывает совместимость с потребителем.</li><li><strong>Окружение.</strong> Запишите версии Node.js, команды и набор тестов. Результат локального запуска нельзя расширять до гарантии другой ОС, базы или feature flag.</li></ol>\n<p>GitHub в руководстве по проверке AI-generated code отдельно выделяет функциональные проверки, контекст и намерение, зависимости, AI-специфичные ошибки и человеческое ревью. Это полезная карта вопросов, но не сертификат качества конкретного diff. NIST SSDF и профиль для generative AI задают практики безопасной разработки и управления рисками; они не подтверждают, что учебный код или конкретная модель безопасны.</p>\n<h2>Что делать при несоответствии</h2>\n<p>При mismatch не исправляйте сразу весь diff новым широким запросом к помощнику. Сначала зафиксируйте наблюдение: какой path лишний, какая строка контракта нарушена, какая проверка отсутствует и какой эффект запрещён. Затем выберите узкое действие.</p>\n<ul><li>Лишний путь — удалить из текущей задачи или открыть отдельный change с новой границей.</li><li>Неверная ветка ошибки — вернуть различие статусов и добавить negative test до повторного ревью.</li><li>Непроверенная зависимость — остановить изменение, проверить пакет и согласовать лицензию.</li><li>Security-sensitive diff — передать его владельцу риска, не объявляя результат безопасным по зелёному lint.</li><li>Неизвестный consumer или runtime — собрать evidence, а до этого оставить решение в статусе <code>stop</code>.</li></ul>\n<p>Остановка не означает автоматический rollback. До merge обычно достаточно сузить кандидат и запросить изменения. После поставки нужна отдельная процедура инцидента и отката, зависящая от системы. Не смешивайте эти решения: gate отвечает на вопрос «можно ли принимать этот diff сейчас», а не «как восстановить production после уже случившегося эффекта».</p>\n<h2>Порядок проверки перед merge</h2>\n<ol><li>Сформулируйте одно решение: принять diff, запросить правки или остановить работу.</li><li>Запишите разрешённые пути, запреты, владельца задачи и чувствительные области.</li><li>Снимите <code>git diff --name-status</code>, прочитайте весь diff и проверьте незапланированные артефакты.</li><li>Для каждой изменённой ветки заполните вход, результат, статус или исключение и запрещённый эффект.</li><li>Найдите тест, который выполняет ветку, и добавьте отрицательный случай для неверного входа, отсутствующего поля или отказа в доступе.</li><li>Проверьте зависимости, схему, сериализацию, права, секреты и окружение.</li><li>Запустите заявленные тесты и <code>git diff --check</code>; сохраните команды и фактический результат.</li><li>Отдельно перечислите неизвестные. Не превращайте отсутствие evidence в pass и не расширяйте доказательство за пределы выполненного запуска.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Этот gate снижает риск пропустить очевидное несоответствие, но не доказывает полноту поиска потребителей, отсутствие уязвимостей или корректность бизнес-решения. Unit test может не увидеть реальный сериализатор, конкурентный вызов, миграцию или конфигурацию в другом окружении. Static analysis может найти подозрительный путь, но не понять, что конкретный статус запрещено менять в этом продукте.</p>\n<p>Пример с <code>invoice.created</code> синтетический: он не подключён к очереди, базе и системе прав. Значения статусов, формат идентификатора и правило дедупликации — иллюстрация, а не универсальный API-контракт. Для платежей, персональных данных, авторизации и миграций нужен профильный владелец, проверка реального окружения и принятый в проекте план отката.</p>\n<p>Не следует приписывать модели результат, которого не измеряли. Без сравнимых задач и заранее заданной метрики этот процесс не доказывает экономию времени, рост качества или отсутствие дефектов. Он лишь делает решение о конкретном diff проверяемым и оставляет явное место для остановки.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Diff можно передавать на решение о merge, когда reviewer показывает: каждый path разрешён задачей; каждая изменённая ветка связана с тестом или другим воспроизводимым evidence; valid, absent и invalid входы не смешаны; запрещённые побочные эффекты проверены; зависимости, данные и права просмотрены; команды и окружение записаны; неизвестные не выданы за доказанный факт. Если хотя бы один пункт не выполнен, корректный результат — запросить конкретную проверку или остановить merge.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://docs.github.com/en/copilot/tutorials/review-ai-generated-code\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub Docs: Review AI-generated code</a> — официальное руководство рекомендует начинать с тестов и статического анализа, сверять контекст и намерение, проверять зависимости, AI-специфичные ошибки и сохранять человеческое ревью. Руководство не подтверждает качество конкретного diff.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/a/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218A: Secure Software Development Practices for Generative AI and Dual-Use Foundation Models</a> — официальный профиль дополняет SSDF практиками для AI-моделей и AI-систем на протяжении жизненного цикла. Это рамка управления рисками, а не проверка учебного кода.</li><li><a href=\"https://csrc.nist.gov/pubs/sp/800/218/final\" target=\"_blank\" rel=\"noopener noreferrer\">NIST SP 800-218: Secure Software Development Framework (SSDF) Version 1.1</a> — официальный набор высокоуровневых практик secure development и общий язык для обсуждения рисков. Он не заменяет доменный контракт, тесты и review владельца системы.</li></ul>"
}