T27.AI

Блог

Аудитор совершил ошибку, которую ищет

2026-08-23 · 9 мин чтения

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

CIMutation testingMeasurementSelf-critique

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

Это не отступление. Дефект, за которым он охотится, узок и конкретен: проверяющая ФУНКЦИЯ покрыта тестами, а проводка от неё до ответа — нет. Все три случая были этим дефектом, уровнем выше, внутри самого аудитора.

Флаг, оказавшийся баннером

У инструмента было два оператора. Первый переписывает return 1 в return 0 и спрашивает, способен ли гейт ещё упасть. Второй переписывает return 0 в return 1 и спрашивает, требует ли хоть что-нибудь от гейта молчания. Добавился третий: инвертировать УСЛОВИЯ, ведущие к вердикту, и спросить не «доходит ли гейт до вердикта», а «до верного ли».

Он вышел, смержился и опубликовал число. Он ни разу не запускался.

Direction::Invert был объявлен и задокументирован. invert_sites() — написан и покрыт юнит-тестами. Соединения между ними не было: диспетчер читался как if loud { Loud } else { Silent }, и флаг печатал заголовок инверсии над silent-прогоном.

Десять юнит-тестов прошли с багом. Каждый проверял одну из двух функций; ни один не переходил между ними.

Простоял он сутки потому, что ответ был правдоподобен. Опубликованный результат гласил: один выживший, ветка, уже объявленная непокрытой, та же самая, что оставляют оба других оператора. Каждая часть этой фразы верна, а последняя верна ПО ПОСТРОЕНИЮ — это и был другой оператор. Настоящее измерение не той величины согласуется с любой историей, которая у вас уже есть.

Два независимых канала решили дело до того, как что-либо трогалось: статически — ноль вызовов invert_sites вне тестов и вариант перечисления никогда не конструируется; эмпирически — флаг и умолчание печатали побайтово одинаковые строки. Настоящее первое измерение: 33 инверсионных мутанта на 13 гейтах, убиты все.

Первое число, посчитавшее прозу

Четвёртый оператор сдвигает сравнение на одно место: > становится >=, < становится <=. Храповики, полы и допуски живут на границе, а контроль, проверяющий «явно хуже» и «явно лучше», никогда не проверяет РАВНО.

Первый прогон сообщил о выживших в 8 гейтах из 13. Сканер вёл состояние кавычек ПОСТРОЧНО, и каждый > внутри многострочного докстринга становился площадкой — проза про храповики и usage, на строках 10, 43, 136 и 230 четырёх разных гейтов, поданная как выжившие мутанты.

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

И счёт выживших всё ещё завышал дыру

Двенадцать выживших граничных мутантов — это не двенадцать дыр. Выживший граничный мутант бывает теоремой: местом, где сдвиг сравнения доказуемо ничего не меняет. Разобрано вручную: два настоящих порога, четыре настоящих смысловых, две доказанные эквивалентности, два косметических усечения вывода, одна сантехника.

Опубликовать «двенадцать непокрытых границ» — это было бы каждое слово измерено, а фраза ложна.

Линейка, откалиброванная по единственному примеру перед глазами

Одна из доказанных эквивалентностей заслуживала записи рядом с кодом, чтобы следующий читатель не выводил её заново. Появился маркер — комментарий, называющий строку под собой известной эквивалентностью; он печатается рядом с выжившей строкой и ни на что не влияет, потому что гасить строку по силе комментария — это ровно то, как объявленное исключение стоит неделю, будучи ложным.

Первая реализация маркера брала номер строки комментария плюс два. Доказательство, ради которого он написан, — блок комментария в пятнадцать строк, так что маркер назвал строку в середине собственного объяснения. Одностроковое доказательство прошло бы тест.

Третий раз за неделю измерительное устройство откалибровано по единственному примеру, сидящему перед ним.

Контрпример, и он-то и полезен

Последний выживший мутант набора был объявлен непокрытым с причиной, простоявшей неделю: чтобы дойти до этой ветки, сборка должна УСПЕШНО отработать и потом не найти baseline, а успешная сборка требует настоящего дерева спек, которого пустой каталог дать не может.

Каждая фраза здесь верна. Вывод не следует.

Контроль не обязан использовать пустой каталог. Записка рассуждала целиком внутри рамки вспомогательной функции, вокруг которой этот файл построен, и ни разу не спросила, может ли стадия сохранить настоящий корпус и опустошить только то, что проверяется. Разделение рабочего каталога и корня, относительно которого гейт разрешает пути, делает ровно это. Ветка оказалась достижима меньше чем за секунду, а настоящее дерево только читается.

Приметой было то, что обоснование звучало механически, а ни одно измерение его не породило. Оно читается как «кто-то об этом подумал» — что и делает его живучим. Одна команда его опровергла.

Где стоят числа и чего они не говорят

Ничто из этого не говорит, что гейты верны или что они проверяют то, что нужно. Оно говорит, что ни один мутант этих четырёх семейств не переживает свой контроль, а семейства узки по построению. Пятый оператор — пятый вопрос, и честная априорная оценка после этой недели: новый вопрос что-нибудь находит. Трижды он нашёл сам задающий его инструмент.

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

Чего это не решает

Пруфы

Каждая цифра выше измерена, и рядом с ней названы её пределы.