{ "index": 103, "slug": "editorial-2025-02-field-ai-code-verification", "title": "Как проверить AI-предложение кода до merge", "excerpt": "Зелёный основной сценарий не доказывает сохранность API, входного состояния и правил доступа. Разбираем три границы, отрицательные проверки и критерий готовности изменения.", "contentHtml": "
Проблема: основной тест проходит, но изменение возвращает не то поле, меняет входной объект или пропускает запрос без роли; цена ошибки — повторная отладка, задержка релиза и риск отказа пользователю.
\\nТезис простой: проверяйте не убедительность AI-предложения и не число зелёных статусов, а границы контракта. Для каждой границы нужна короткая цепочка доказательств: что обещает код, что наблюдает проверка, какой отрицательный путь рассмотрен и какое действие следует из результата. Если сигналы относятся к разным контрактам, merge нельзя считать готовым.
\\nКонтракт описывает неизменяемое условие. Это форма результата, сохранность аргумента или список разрешённых ролей. Статический анализ показывает структуру кода. Позитивный тест показывает разрешённый путь. Негативный тест показывает отказ. Review связывает эти наблюдения с контекстом вызова. Ручной сценарий проверяет эффект на границе системы.
\\nНи один сигнал не даёт общего вердикта. Линтер не знает, какое имя поля ждёт потребитель. Тест строки не видит мутацию объекта. Успешный сценарий редактора не доказывает запрет для пустой роли. Поэтому сначала назовите риск как наблюдаемый разрыв, затем выберите проверку, которая может его опровергнуть.
\\nMapper получает subtotalCents и taxCents. Он правильно складывает их. Потребитель ожидает объект с полем amountCents, но предложенный код возвращает total. Тест арифметики остаётся зелёным: число не изменилось. Ошибка появляется на границе потребителя.
Проверка должна сравнить не только значение, но и публичную форму. Нужны assertion на ключи результата и тест, который читает объект так же, как реальный потребитель. Если переименование действительно нужно, его оформляет владелец API. Нельзя переписать тест под новое поле и объявить проблему решённой: так тест закрепит решение, которого ещё никто не принял.
\\nФункция с именем preview должна вычислить результат и вернуть его. Внутри она выполняет draft.status = 'normalized'. Caller передал свой объект и ожидает, что после preview он останется прежним. Проверка ответа не замечает побочный эффект, потому что строка выглядит правильно.
Здесь нужен снимок входа до вызова и сравнение после вызова. Статическое правило может искать присваивание аргументу, но оно не заменяет проверку владения данными. Исправление — создать производное значение без мутации или вынести изменение в отдельную явно названную операцию. Название функции не является доказательством поведения.
\\nКонтракт разрешает только роль editor. Условие actorRole !== 'viewer' блокирует viewer, но пропускает запрос без роли. Тест для editor сообщает только один факт: разрешённый путь работает. Даже тест для viewer не подтверждает, что отсутствие значения отклоняется.
Явный allow-list делает правило проверяемым:
\\nfunction canEdit(actorRole) {\\n const allowedRoles = new Set(['editor']);\\n return allowedRoles.has(actorRole);\\n}\\n\\nexpect(canEdit('editor')).toBe(true);\\nexpect(canEdit('viewer')).toBe(false);\\nexpect(canEdit()).toBe(false);\\nЭто учебный пример малого контракта. Он не проверяет токены, identity provider, tenant boundary, сессии или threat model реального приложения. Он показывает другое: разрешённое значение задано явно, а отрицательная ветка входит в контракт. Для production нужны отдельные проверки всей цепочки авторизации.
\\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Число верно, потребитель не находит поле | Проверили значение, но не форму результата | Сравнить ключи и выполнить тест через потребитель | Вернуть имя поля или принять отдельное решение о совместимости |
| Preview меняет draft | Вычисление смешано с мутацией входа | Сравнить объект до и после; проверить присваивания аргументу | Создать производное значение или назвать мутацию отдельной операцией |
| Запрос без роли проходит | Условие перечисляет исключение, а не разрешённые роли | Добавить проверку отсутствующей роли и прочитать ветви от allow | Оставить явный allow-list и default-deny |
| Все статусы зелёные, но scope различается | Проверки относятся к разным контрактам | Сопоставить вход, выход и границу каждого сигнала | Заблокировать merge до единого scope или решения владельца |
preview, format и calculate отдельно подтвердите отсутствие мутации.Stop означает только одно: не продолжать подготовку merge, пока доказательства расходятся. Он не меняет историю системы и не восстанавливает доставленное состояние. Revert отменяет конкретное изменение в истории версий. Rollback возвращает уже работающую систему к прежнему состоянию и требует отдельной проверки данных, scope и полномочий. Называть любой block rollback нельзя: это создаёт видимость готового пути восстановления.
\\nHuman approval нужен после ясной формулировки выбора. Например, владелец может сохранить старое поле ради совместимости, принять переименование с обновлением потребителей или потребовать исправить правило доступа. Approval не закрывает пробел в доказательствах. Если reviewer не может назвать контракт, границу пользователя и остаточный риск, вопрос ещё не готов.
\\nПримеры в статье учебные. В них нет настоящего репозитория, CI, токенов, пользователей, telemetry или production-нагрузки. Имена полей, ролей и функций подобраны для объяснения механизма. Из зелёного учебного теста нельзя вывести отсутствие уязвимости в реальной авторизации. Из одной цепочки доказательств нельзя вывести готовность релиза.
\\nОфициальная документация инструментов тоже имеет границы. GitHub описывает code review как комментарии и рекомендации, а обязательное решение об изменении остаётся за процессом команды. NIST задаёт практики безопасной разработки, но не выбирает ваши контракты и владельцев риска. OWASP подчёркивает пользу ручной проверки там, где автоматические средства не видят бизнес-логику и контекст. Эти источники поддерживают многослойную проверку, но не подтверждают конкретный учебный пример.
\\nИзменение готово к approval, если для каждой затронутой границы выполнены четыре условия: контракт записан; основной и отрицательный пути проверены; входное состояние не меняется без явного разрешения; каждый сигнал относится к тому же scope, что и diff. В записи есть вердикт merge, revise или block. Для merge указан владелец решения. Для block указано доказательство, которое снимет блокировку. Если хотя бы одно условие не выполнено, готовность не доказана.