DarkRiDDeR10 мин

Независимые доказательства ловят разные классы ошибок

AIКачество

У generated diff может быть одновременно зелёный линтер, зелёный unit test и неверное решение. Цена ошибки не в том, что один инструмент «не сработал». Цена в ложной уверенности: три сигнала повторяют один happy path, команда считает риск закрытым и замечает нарушение контракта только после следующего потребителя. В синтетическом примере это повторная проверка; в реальной системе размер ущерба нельзя вывести из числа зелёных индикаторов.

Причина — смешение классов ошибок. Static check хорошо ищет форму, тест — заранее названное поведение, review — намерение и контекст, manual reproduction — путь одного потребителя. Если все четыре свидетельства отвечают на вопрос «функция вернула число», ни одно не отвечает на вопрос «сохранилось ли поле публичного API». Механизм проверки должен явно связывать risk с evidence и оставлять пустые клетки видимыми.

Матрица важнее списка инструментов

Я не начинаю с названий инструментов. Сначала в первой колонке записываю risk как наблюдаемый разрыв: поле переименовано, вход мутируется, неуказанная роль допускается, обработка ошибки пропущена. Во второй колонке — evidence, способное опровергнуть именно это утверждение. В третьей — scope: что этот evidence видит. В четвёртой — stop condition. Тогда команда сравнивает не бренды и не «уровни автоматизации», а стоимость закрытия конкретной неопределённости.

Матрица риска и доказательств: contract, static check, focused test, human review и manual reproduction подсвечивают разные классы ошибок и не образуют универсальную гарантию.
Матрица показывает пересечения и пробелы. Зелёная клетка означает «может дать релевантное свидетельство», а не «гарантирует отсутствие ошибки».
Risk → detecting evidence в fixed synthetic cases
РискНаиболее прямое evidenceПолезное пересечениеЛожная уверенностьStop condition
Переименование публичного поляcontract assertion с expected shapeconsumer-oriented focused test и reviewтест проверяет только число, а не имя поляcontract и test описывают разные result shape
Мутация caller-owned inputinput before/after assertionstatic assignment rule и review ownershiprendered output верный, поэтому side effect не смотрятoutput green, input boundary не доказана
Missing role допускаетсяnegative test для отсутствующего значенияallow-list review и static patterneditor-only test трактуют как access proofdefault-deny и условие расходятся
Возможная security weaknessthreat-aware review и relevant testanalysis rule, если риск формализованлинтер или один test объявляют security provenриск требует отсутствующего context или authority

Независимость не означает «не похожи»

Два evidence независимы для решения не потому, что их сделали разные люди или они называются по-разному. Они независимы, когда могут опровергнуть разные предпосылки. Contract check и consumer test частично пересекаются: оба смотрят на форму результата. Но первый проверяет declared shape, а второй — использование конкретным потребителем. Static assignment rule и input-equality test тоже пересекаются, но один находит прямую запись в тексте, другой видит наблюдаемый эффект fixed call. Их полезно держать вместе, пока оба остаются короткими.

Не нужно притворяться, что overlap — дефект. Пересечение снижает риск того, что одна опечатка в test fixture останется незамеченной. Проблема начинается, когда overlap маскируют под независимость. Например, generated code и generated test могут повторить одно неправильное предположение: «роль отсутствует, значит это не viewer». Второй тест тогда лишь умножает доверие к той же ветке. Добавить human review полезно, если reviewer получает contract и может задать вопрос, которого нет в prompt или test name.

Компактный fixed synthetic model

Модель ниже не классифицирует реальный код. Она принимает только три закреплённые карточки и на совпадении фиксирует disagreement. Если в report появилась лишняя колонка, sparse array, цикл или подменённое finding, следующий шаг возвращает closed plan. Это намеренно консервативное поведение: сомнительное evidence не становится «почти достаточным».

import {
  createFixedSyntheticVerificationInput,
  inspectSyntheticGeneratedDiff,
  buildSyntheticVerificationPlan,
} from './upgrade-2025-02.mjs';

const input = createFixedSyntheticVerificationInput('hidden-side-effect');
const report = inspectSyntheticGeneratedDiff(input);
const plan = buildSyntheticVerificationPlan(report);

console.log(report.evidence.map(({ id }) => id));
// [ contract, static, focused-test, review, manual-reproduction ]
console.log(plan.humanApproval);
// required-after-evidence-agrees-and-outside-this-model

Здесь output preview может быть правильным, но mutable input нарушает ownership boundary. Static check находит присваивание, focused test должен сравнить input до и после, reviewer соотносит имя preview с поведением, manual reproduction показывает, что caller видит новое значение после вызова. Ни одно свидетельство не доказывает безопасность функции. Вместе они только делают конкретный риск наблюдаемым и объясняют, почему merge нельзя продолжить без решения.

Где возникает false confidence

Первая ловушка — считать количество зелёных запусков мерой correctness. Три теста одного валидного значения всё ещё не проверяют отсутствующее значение, старый consumer или mutation. Вторая — считать отсутствие finding результатом анализа. Линтер без правила для контракта не говорит, что контракт сохранён. Третья — считать хороший review comment доказательством: comment может быть точным, но не иметь acceptance criterion и не сопровождаться test. Четвёртая — превращать manual reproduction в substitute for automated evidence, хотя ручной шаг трудно повторять без входа, ожидаемого результата и владельца.

Цена каждого способа разная. Static check быстро масштабируется после того, как риск стал формальным, но требует поддержки правила и даёт false positive. Focused test стоит времени на фикстуру, зато делает один expected path повторяемым. Human review медленнее и дороже на единицу diff, но может заметить скрытую границу или неопределённое решение. Manual reproduction полезна для короткого consumer scenario, но не масштабируется как постоянный gate. Поздний автор не выбирает «самый современный» способ: он выбирает самый дешёвый evidence, который способен опровергнуть текущую гипотезу.

Компромисс способов проверки
СпособСтоимость для маленького diffСильная сторонаНужно добавить, когда
Static checkнизкая после настройки правилаповторяемый явный паттерннужен intent или runtime path
Focused testсредняя: fixture и expected resultодна stated branchесть другой consumer, state или boundary
Human reviewсредняя/высокая: внимание владельцаконтекст, policy и незаданный вопросdiff крупный или contract не записан
Manual reproductionнизкая для одного случая, высокая при масштабированиивидимый путь потребителясценарий должен стать regression test

Scope и stop condition

Scope — это не название модуля. Для contract-mismatch scope — один fixed result shape. Для hidden-side-effect — ownership одного argument. Для incomplete-test — три значения fixed role. Как только review пытается сделать из них оценку production traffic, поведения модели или состояния доступа, он выходит за границу evidence. Правильное действие — записать unknown и открыть отдельное authorized investigation, а не продолжать merge по аналогии.

Stop condition должен быть машинально читаемым человеком: «contract, negative test и reviewer rationale расходятся», «input boundary не проверена», «default-deny не выражен в условии». Формулировка «не нравится diff» не годится, потому что не даёт следующего шага. Формулировка «получилось зелёное» тоже не годится, потому что не называет рассмотренный risk. Стоп не доказывает, что diff плох; он говорит, что текущий набор evidence не имеет права на решение.

Короткий порядок работы с матрицей

  1. Назовите риск наблюдаемым разрывом. Не «AI ошибся», а «публичное поле исчезло», «input изменился» или «отсутствующее значение получило доступ».
  2. Выберите прямое evidence. Оно должно уметь опровергнуть именно эту формулировку, а не просто добавить ещё один зелёный статус.
  3. Найдите повтор предпосылки. Если code и test исходят из одного неверного правила, добавьте contract assertion, reviewer question или negative case.
  4. Запишите stop. При расхождении сохраните known и unknown, затем передайте следующий вопрос владельцу границы.

Что говорят источники, а чего не говорят

NIST SP 800-218 в PW.7 связывает выбор review и analysis со стадией разработки, а findings — с triage в рабочем процессе. Это поддерживает мысль о scope и фиксации расхождений. OWASP Code Review Guide описывает сочетание инструментов и человеческой проверки, отмечая, что tools не понимают весь context. Документация GitHub Copilot code review предупреждает о missed problems, false positives и возможной небезопасности suggestions. Ни один источник не устанавливает волшебный набор из пяти gates и не подтверждает, что матрица автоматически покрывает security или correctness.

Ограничения и следующий проверяемый шаг

В этой статье risk names, diff fragments, tests, verdicts, labels, роли и «стоимость» — только fixed synthetic учебные данные. Нет model inference, API, prompt execution, source search, Git, CI, telemetry, production, пользователей, секретов или измерения времени. Контроль, который дал evidence в карточке, может не существовать в вашем стеке; добавлять его стоит только после того, как владелец подтвердит contract и допустимый scope.

Следующий шаг: на одном diff сделайте две таблицы. В первой свяжите каждый риск с самым прямым evidence. Во второй напишите, какие два evidence повторяют одну предпосылку. Затем добавьте один negative case или reviewer question, который может опровергнуть эту предпосылку. Если его нельзя назвать без изучения реальной системы, остановите шаблонный merge и запросите контекст у владельца. Ожидаемый результат — не «больше контроля», а самостоятельное доказательство для каждой важной границы.

Историческая граница февраля 2025

Source set закреплён состоянием до февраля 2025: GitHub Docs commit от 12 декабря 2024, NIST SSDF final 2022 и OWASP Guide 2.0 2017. В статье не используются сведения о версиях моделей, агентах, security scoring или инструментах после этой даты.

Проверяемые источники

  • GitHub Docs: Responsible use of GitHub Copilot code review (immutable GitHub Docs commit 7b3918e77baf865d1f16bd60e570acea874ee9eb, 12 December 2024) — В документе сказано, что review дополняет, а не заменяет внимательную проверку человеком; подсказка может быть неточной, синтаксически или семантически неверной либо небезопасной. Ограничение: Это документация конкретного preview-инструмента на зафиксированной ревизии. Она не измеряет качество любого другого ассистента и не даёт правила merge для конкретной команды.
  • NIST SP 800-218, Secure Software Development Framework Version 1.1 (NIST SP 800-218 Version 1.1, final 3 February 2022) — PW.7 предлагает выбирать review и/или code analysis по стадии работы, проводить их по secure-coding standard и фиксировать найденные вопросы и рекомендации; результаты тестирования могут быть входом peer review. Ограничение: SSDF задаёт высокоуровневую рамку. Он не выбирает линтер, не доказывает покрытие, не определяет риск конкретного diff и не отменяет локальные полномочия.
  • OWASP Code Review Guide 2.0 (OWASP Code Review Guide 2.0, July 2017 release PDF) — Руководство описывает code review как проверку присутствия и корректного вызова security и logical controls; инструменты полезны для масштаба, но контекст и подтверждение результата остаются задачей человека. Ограничение: Это общее руководство по secure review, а не каталог правил для языка, фреймворка или модели. Оно не обещает, что review найдёт все уязвимости.