diff --git a/apps/website/src/data/blog/posts.ts b/apps/website/src/data/blog/posts.ts index e6bb176a74..793cba3631 100644 --- a/apps/website/src/data/blog/posts.ts +++ b/apps/website/src/data/blog/posts.ts @@ -686,7 +686,87 @@ const greenCiUnusable: Post = { }, } -export const posts: Post[] = [greenCiUnusable, scaleFieldWidth, openGigabitEthernet] +// The sequel to greenCiUnusable, and it only exists because that post's open +// questions got answered. The numbers are from the CI logs of the three +// repositories named; the pull requests are linked with their states. +const repairDoesNotPropagate: Post = { + slug: 'a-repair-reaches-only-the-copy-it-lands-in', + title: 'Sixteen defects fixed, zero of them reached the consumer', + summary: + 'Two repositories carried the same six files under the same names. Repairing one changed nothing downstream, and no instrument in either could report why — because each copy compiles entirely on its own.', + date: '2026-08-12', + readingMinutes: 7, + tags: ['Zig', 'Modularity', 'CI', 'Verification'], + receipts: [ + { label: 'zig-golden-float #97 — 16 defects repaired · MERGED 2026-08-11', href: 'https://github.com/gHashTag/zig-golden-float/pull/97' }, + { label: 'zig-hdc #3 — one implementation, CI green · MERGED 2026-08-12', href: 'https://github.com/gHashTag/zig-hdc/pull/3' }, + { label: 'zig-hdc #2 — the earlier attempt, closed as superseded', href: 'https://github.com/gHashTag/zig-hdc/pull/2' }, + ], + openQuestions: [ + 'gHashTag/trinity, the consumer that started this, is still red. Its pin predates the repair and its own build has other causes, so nothing here claims that chain is finished.', + 'Listing re-exported names one by one has a cost this post does not pretend away: a symbol added upstream does not appear downstream until somebody adds it. usingnamespace, which would have avoided that, was removed in Zig 0.15.', + 'Whether the eighteen files that stayed behind should also live upstream was not decided. They have no counterpart there today; that is a fact about today, not an argument.', + ], + body: [ + { kind: 'p', text: 'I spent a night repairing sixteen defects in a package, watched its CI go green for the first time in its life, re-pinned the package that depends on it — and the consumer did not improve by a single error. Not one.' }, + { kind: 'p', text: 'The first explanation to reach for is a stale artefact. It was not that: pinning the exact merge commit produced the same hash the CDN had already served, so the bytes arriving were the repaired bytes.' }, + { kind: 'h', text: 'Both repositories owned the same files' }, + { kind: 'p', text: 'The consumer carried its own src/vsa/core.zig, common.zig, concurrency.zig, 10k_vsa.zig, hrr.zig and fpga_bind.zig. So did the package below it. Same names, same purpose, separate files, edited independently since the migration that split them apart.' }, + { kind: 'quote', text: 'The fixes went into different files with the same names. Nothing was wrong with the repair; it simply landed somewhere else.' }, + { kind: 'p', text: 'That is why no instrument reported it. Each copy compiles on its own. The package below went green because its copies were repaired; the consumer stayed red because its copies were not; and neither build has any way to notice that the other exists.' }, + { kind: 'h', text: 'The decision was arithmetic, not taste' }, + { kind: 'p', text: 'Choosing which copy to keep looks like a judgement call, and it stopped being one after five minutes of measurement: comparing the two public surfaces symbol by symbol, they are identical apart from a single constant. Neither is more capable. Nothing is lost by keeping either — so the direction follows the dependency that already exists, and the copies that build and pass 267 tests win over the copies that do not.' }, + { kind: 'p', text: 'Each duplicated file became a re-export rather than a deletion. Twenty-three relative imports across the consumer keep working unchanged, nothing else in the tree moves, and re-divergence stops being discouraged and becomes impossible: there is one implementation behind the path now.' }, + { kind: 'h', text: 'What the deduplication then exposed' }, + { kind: 'p', text: 'With one implementation in place and refAllDeclsRecursive forcing the whole public surface through the compiler, three more things surfaced, none of them caused by the change:' }, + { kind: 'ul', items: [ + 'src/vsa.zig re-exported concurrency.LockFreePool, a name that has never existed in either copy. It referred to nothing, and nothing complained, because lazy analysis never asked what it pointed at.', + 'The pin predated the repair, so the sixteen fixed defects were still arriving through the dependency.', + 'text_encoding.zig passed an allocator to bundle2 and to add at nine call sites, against an API that had moved without it, and bound a const where add needs a mutable pointer.', + ] }, + { kind: 'p', text: 'Every one of those had been in the repository for as long as the files had, invisible for the same reason: nothing referenced them, so nothing compiled them.' }, + { kind: 'h', text: 'The general form' }, + { kind: 'p', text: 'Parnas gave the criterion in 1972 and the reason he gave was changeability: each design decision should have exactly one home. The corollary is arithmetic. A decision living in n places must be repaired n times; the repairs do not propagate; and no instrument inside either copy reports the omission, because each compiles or fails entirely on its own.' }, + { kind: 'p', text: 'So divergence is not a risk that duplication carries. It is what duplication is. The question a copy raises is not whether the two will drift but how long before somebody notices, and the answer here was four months and a consumer that could not build.' }, + { kind: 'p', text: 'None of which makes vendoring wrong. A pinned copy is a deliberate trade — insulation from an upstream you do not control, paid for in repairs you now owe twice — and it is defensible when it is chosen and written down. What has no defence is duplication nobody decided on, which is exactly what a migration leaves behind when it copies rather than moves.' }, + ], + published: true, + ru: { + title: 'Шестнадцать дефектов исправлены, до потребителя не дошёл ни один', + summary: + 'Два репозитория несли одни и те же шесть файлов под одними именами. Починка одного не изменила ничего внизу по цепочке, и ни один прибор не мог сказать почему — потому что каждая копия компилируется сама по себе.', + openQuestions: [ + 'gHashTag/trinity, потребитель, с которого всё началось, всё ещё красный. Его закрепление старше починки, и у его сборки есть другие причины, так что ничто здесь не утверждает, что цепочка завершена.', + 'Перечисление реэкспортируемых имён поимённо имеет цену, и этот пост её не прячет: символ, добавленный наверху, не появится внизу, пока его туда не добавят. usingnamespace, который избавил бы от этого, убран в Zig 0.15.', + 'Должны ли восемнадцать оставшихся файлов тоже жить наверху — не решено. Сегодня у них там нет соответствия; это факт о сегодня, а не аргумент.', + ], + body: [ + { kind: 'p', text: 'Я провёл ночь, исправляя шестнадцать дефектов в пакете, увидел, как его CI впервые в жизни позеленел, перезакрепил пакет, который от него зависит, — и у потребителя не убавилось ни одной ошибки. Ни одной.' }, + { kind: 'p', text: 'Первое объяснение, к которому тянется рука, — устаревший архив. Дело не в нём: закрепление точного коммита слияния дало тот же хеш, что CDN уже отдавал, значит приходили именно починенные байты.' }, + { kind: 'h', text: 'Оба репозитория владели одними и теми же файлами' }, + { kind: 'p', text: 'У потребителя были свои src/vsa/core.zig, common.zig, concurrency.zig, 10k_vsa.zig, hrr.zig и fpga_bind.zig. И у пакета под ним — тоже. Одинаковые имена, одинаковое назначение, разные файлы, правившиеся независимо с того переноса, который их разделил.' }, + { kind: 'quote', text: 'Правки легли в разные файлы с одинаковыми именами. С починкой всё было в порядке — она просто оказалась не там.' }, + { kind: 'p', text: 'Поэтому ни один прибор об этом не сообщил. Каждая копия компилируется сама по себе. Пакет внизу позеленел, потому что починили его копии; потребитель остался красным, потому что его — нет; и ни у одной сборки нет способа заметить, что вторая вообще существует.' }, + { kind: 'h', text: 'Решение было арифметикой, а не вкусом' }, + { kind: 'p', text: 'Выбор, какую копию оставить, выглядит как суждение — и перестаёт им быть после пяти минут измерения: если сверить обе публичные поверхности символ за символом, они совпадают с точностью до одной константы. Ни одна не богаче. Терять нечего ни при каком выборе — поэтому направление задаёт уже существующая зависимость, и копии, которые собираются и проходят 267 тестов, побеждают копии, которые не собираются.' }, + { kind: 'p', text: 'Каждый дублирующийся файл стал реэкспортом, а не удалением. Двадцать три относительных импорта у потребителя продолжают работать как работали, остальное дерево не двигается, а повторное расхождение перестаёт быть нежелательным и становится невозможным: за путём теперь одна реализация.' }, + { kind: 'h', text: 'Что вскрылось после развязки' }, + { kind: 'p', text: 'Когда реализация осталась одна, а refAllDeclsRecursive прогнал через компилятор всю публичную поверхность, всплыли ещё три вещи — и ни одна из них не вызвана этой правкой:' }, + { kind: 'ul', items: [ + 'src/vsa.zig реэкспортировал concurrency.LockFreePool — имя, которого никогда не было ни в одной копии. Оно указывало в никуда, и никто не возражал, потому что ленивый анализ ни разу не спросил, куда именно.', + 'Закрепление было старше починки, поэтому шестнадцать исправленных дефектов по-прежнему приходили через зависимость.', + 'text_encoding.zig передавал аллокатор в bundle2 и в add в девяти местах — API уехал без него, — и связывал const там, где add требует изменяемый указатель.', + ] }, + { kind: 'p', text: 'Всё это лежало в репозитории ровно столько же, сколько сами файлы, и было невидимо по одной причине: на это никто не ссылался, значит это не компилировалось.' }, + { kind: 'h', text: 'Общая форма' }, + { kind: 'p', text: 'Парнас сформулировал критерий в 1972 году, и причина у него была именно изменяемость: у каждого проектного решения должен быть ровно один дом. Следствие — арифметическое. Решение, живущее в n местах, требует n починок; починки не распространяются; и ни один прибор внутри любой из копий не сообщит о пропуске, потому что каждая компилируется или падает сама по себе.' }, + { kind: 'p', text: 'Значит расхождение — не риск, который несёт дублирование. Расхождение и есть дублирование. Копия ставит вопрос не о том, разойдутся ли двое, а о том, через сколько это заметят; здесь ответ — четыре месяца и потребитель, который не собирался.' }, + { kind: 'p', text: 'Ничто из этого не делает вендоринг неправильным. Закреплённая копия — осознанный размен: изоляция от чужого репозитория, за которую платят починками в двойном размере, — и он защитим, когда выбран и записан. Не имеет защиты дублирование, о котором никто не принимал решения, — а именно его оставляет за собой перенос, который копирует вместо того, чтобы перемещать.' }, + ], + }, +} + +export const posts: Post[] = [repairDoesNotPropagate, greenCiUnusable, scaleFieldWidth, openGigabitEthernet] export const publishedPosts = () => posts.filter((p) => p.published) diff --git a/apps/website/src/data/verificationScience.ts b/apps/website/src/data/verificationScience.ts index 3809bbc119..edada7bbc2 100644 --- a/apps/website/src/data/verificationScience.ts +++ b/apps/website/src/data/verificationScience.ts @@ -212,6 +212,18 @@ export const THEOREMS: Theorem[] = [ doesNotClaim: 'That proxies are never worth using. The direct test costs a subprocess per sampled page and the proxy cost one comparison, which is why the proxy was written first and why it survived until a confound arrived. What the theorem forbids is treating the two as interchangeable once the confound is known — and the moment to look for confounds is when the check disagrees with something you can verify by hand.', }, + { + id: 'T18', + name: 'A repair reaches only the copy it lands in', + statement: + 'Parnas’s criterion for decomposing a system is that each design decision should have exactly one home, because changeability is what the decomposition is for. The corollary is arithmetic rather than stylistic: a decision living in n places must be repaired n times, the repairs do not propagate, and no instrument inside either copy reports the omission — each one compiles, or fails, entirely on its own. Divergence is therefore not a risk that duplication carries; it is what duplication is.', + worked: + 'Sixteen defects were repaired in one package and the consumer that depends on it did not improve by a single error. Not a caching artefact — pinning the exact merge commit produced the same hash the CDN had already served. Both repositories carried their own src/vsa/core.zig, common.zig, concurrency.zig, 10k_vsa.zig, hrr.zig and fpga_bind.zig, edited independently since the migration that separated them, so the fixes went into different files with the same names. Comparing the two public surfaces symbol by symbol showed them identical, one constant apart, which is what made the choice arithmetic instead of a matter of taste: nothing was lost by keeping one. Replacing the duplicates with re-exports of the repaired implementation took the consumer from five errors to green.', + citation: 'Parnas, “On the Criteria To Be Used in Decomposing Systems into Modules”, CACM 15(12), 1972', + url: 'https://doi.org/10.1145/361598.361623', + doesNotClaim: + 'That vendoring is always wrong. A pinned copy is a deliberate trade — insulation from an upstream you do not control, paid for in repairs you now owe twice — and it is defensible when it is chosen and written down. What has no defence is duplication nobody decided on, which is the state a migration leaves behind when it copies rather than moves. Nor does a re-export make the boundary free: names must now be listed one by one, and a symbol added upstream does not appear downstream until somebody adds it.', + }, ] export const SCIENCE_INTRO_EN =