T27.AI

Блог

Шестнадцать дефектов исправлены, до потребителя не дошёл ни один

2026-08-12 · 7 мин чтения

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

ZigModularityCIVerification

Я провёл ночь, исправляя шестнадцать дефектов в пакете, увидел, как его CI впервые в жизни позеленел, перезакрепил пакет, который от него зависит, — и у потребителя не убавилось ни одной ошибки. Ни одной.

Первое объяснение, к которому тянется рука, — устаревший архив. Дело не в нём: закрепление точного коммита слияния дало тот же хеш, что CDN уже отдавал, значит приходили именно починенные байты.

Оба репозитория владели одними и теми же файлами

У потребителя были свои src/vsa/core.zig, common.zig, concurrency.zig, 10k_vsa.zig, hrr.zig и fpga_bind.zig. И у пакета под ним — тоже. Одинаковые имена, одинаковое назначение, разные файлы, правившиеся независимо с того переноса, который их разделил.

Правки легли в разные файлы с одинаковыми именами. С починкой всё было в порядке — она просто оказалась не там.

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

Решение было арифметикой, а не вкусом

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

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

Что вскрылось после развязки

Когда реализация осталась одна, а refAllDeclsRecursive прогнал через компилятор всю публичную поверхность, всплыли ещё три вещи — и ни одна из них не вызвана этой правкой:

Всё это лежало в репозитории ровно столько же, сколько сами файлы, и было невидимо по одной причине: на это никто не ссылался, значит это не компилировалось.

Общая форма

Парнас сформулировал критерий в 1972 году, и причина у него была именно изменяемость: у каждого проектного решения должен быть ровно один дом. Следствие — арифметическое. Решение, живущее в n местах, требует n починок; починки не распространяются; и ни один прибор внутри любой из копий не сообщит о пропуске, потому что каждая компилируется или падает сама по себе.

Значит расхождение — не риск, который несёт дублирование. Расхождение и есть дублирование. Копия ставит вопрос не о том, разойдутся ли двое, а о том, через сколько это заметят; здесь ответ — четыре месяца и потребитель, который не собирался.

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

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

Пруфы

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