Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import type { Block } from '../types'

export const body: Block[] = [
{
kind: 'p',
text: 'A CI job in trinity-fpga compiles every file that has been migrated to Zig 0.16, one at a time, on Linux. It exists for a specific reason: an earlier version of the same check passed on a macOS laptop and failed on the runner, because macOS links libc implicitly and Linux does not. So the target was pinned rather than left native, and the comment beside it said the pin was "a no-op on this runner."',
},
{
kind: 'p',
text: 'The pin was x86_64-linux. That is not the runner\'s libc. Zig resolves a Linux target with no ABI suffix to musl, and the runner — and every real build of this project — is glibc.',
},
{ kind: 'h', text: 'The three targets' },
{
kind: 'p',
text: 'Reproduce it with a file that does nothing. Zig 0.16.0, cross-compiling from macOS aarch64, one empty main and -lc:',
},
{
kind: 'code',
text: 'printf \'pub fn main() void {}\\n\' > t.zig\nfor t in x86_64-linux x86_64-linux-gnu x86_64-linux-musl; do\n zig build-exe t.zig -target $t -lc -femit-bin=out_$t\n file out_$t\ndone',
},
{
kind: 'table',
head: ['-target', 'file(1) reports'],
rows: [
['x86_64-linux', 'statically linked'],
['x86_64-linux-gnu', 'dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2'],
['x86_64-linux-musl', 'statically linked'],
],
},
{
kind: 'p',
text: 'The first row and the third row behave the same way and the middle one does not. Static linkage with no interpreter is the musl build; the glibc build carries a dynamic loader in its program headers. The unsuffixed target sits with musl, and the only target triple that appears as a string anywhere in the unsuffixed binary is x86_64-linux-musl — Zig records the resolved triple, not the one you typed.',
},
{
kind: 'p',
text: 'Whether that difference can reach a real file is a question about the standard library. In the 0.16.0 install used here, std/c.zig contains eleven isGnu() or isMusl() call sites:',
},
{
kind: 'code',
text: 'grep -cE \'\\.isGnu\\(\\)|\\.isMusl\\(\\)\' "$(dirname "$(readlink -f "$(which zig)")")/../lib/zig/std/c.zig"\n11',
},
{ kind: 'h', text: 'What the gate could not have caught' },
{
kind: 'p',
text: 'A file that reaches one of those switches on the musl side compiles cleanly under x86_64-linux and fails under x86_64-linux-gnu. The job would have gone green and the real build would have gone red — which is the exact failure the job was created to prevent, moved one layer down. A check written against platform-blind verification was itself verifying against a platform nobody ships.',
},
{
kind: 'p',
text: 'The fix is one suffix. The pin is now x86_64-linux-gnu, with the measurement written into the comment so the three characters do not read as decoration to whoever edits the line next. The false claim was corrected in place rather than deleted: "this used to be wrong and here is why" survives a later reader better than silence does.',
},
{ kind: 'h', text: 'One claim in the pull request does not reproduce' },
{
kind: 'p',
text: 'The pull request describes the musl-suffixed build as "byte-identical" to the unsuffixed one. That is not checkable this way, and the reason is worth more than the claim. Building the identical target three times, into three separate directories with the identical output name, gives three different binaries:',
},
{
kind: 'table',
head: ['build of -target x86_64-linux', 'size (bytes)', 'sha256 prefix'],
rows: [
['first', '12,731,133', '468ce3a1'],
['second', '12,731,154', '6998eff5'],
['third', '12,731,133', '1d387f61'],
],
},
{
kind: 'p',
text: 'Three hashes, two sizes, from one command run three times. So the hash cannot discriminate between targets here at all — it does not even discriminate a target from itself, and neither does the size. What survives that control is the linkage reported by file(1) and the resolved triple recorded in the binary. Those two are the evidence; the hash never was.',
},
{
kind: 'p',
text: 'This does not weaken the correction. It replaces a strong-sounding piece of support with a weaker piece that is actually true, which is the direction that costs nothing later.',
},
{ kind: 'h', text: 'Three commits, seventy minutes' },
{
kind: 'p',
text: 'The workflow file has exactly three commits, and two of them are corrections to the first:',
},
{
kind: 'table',
head: ['PR', 'merged (UTC)', 'what was wrong'],
rows: [
['#746', '2026-09-05 18:38:29', 'the job is introduced'],
['#747', '2026-09-05 18:57:37', 'its predicate tested the type, not the entry point'],
['#749', '2026-09-05 19:48:52', 'its target resolved to musl, not glibc'],
],
},
{
kind: 'p',
text: 'Seventy minutes and twenty-three seconds from introduction to the second correction. Neither defect was visible in the YAML; both were found by extracting the step body and running it verbatim against the tree. Reading a gate tells you what it was meant to check. Running it tells you what it checks.',
},
{ kind: 'h', text: 'What this does not establish' },
{
kind: 'p',
text: 'No file in this tree was shown to depend on a musl-only symbol. The finding is that the gate could not have caught one, not that one is there. The target resolution was measured on one host cross-compiling, not on the runner; the runner is inferred to behave the same because it fetches the same Zig version. And the job compiles files — it does not run them. A file that links against the right libc has not thereby been shown to work.',
},
]

export const ruBody: Block[] = [
{
kind: 'p',
text: 'CI-задача в trinity-fpga компилирует по одному каждый файл, переведённый на Zig 0.16, под Linux. Она появилась по конкретной причине: более ранняя версия той же проверки прошла на ноутбуке с macOS и упала на раннере, потому что macOS линкует libc неявно, а Linux — нет. Поэтому цель зафиксировали вместо нативной, а комментарий рядом утверждал, что фиксация «ничего не меняет на этом раннере».',
},
{
kind: 'p',
text: 'Зафиксирована была x86_64-linux. Это не тот libc, что у раннера. Zig разрешает Linux-цель без суффикса ABI в musl, а раннер — и любая настоящая сборка этого проекта — это glibc.',
},
{ kind: 'h', text: 'Три цели' },
{
kind: 'p',
text: 'Воспроизводится файлом, который не делает ничего. Zig 0.16.0, кросс-компиляция с macOS aarch64, пустой main и -lc:',
},
{
kind: 'code',
text: 'printf \'pub fn main() void {}\\n\' > t.zig\nfor t in x86_64-linux x86_64-linux-gnu x86_64-linux-musl; do\n zig build-exe t.zig -target $t -lc -femit-bin=out_$t\n file out_$t\ndone',
},
{
kind: 'table',
head: ['-target', 'что сообщает file(1)'],
rows: [
['x86_64-linux', 'статически слинкован'],
['x86_64-linux-gnu', 'динамически слинкован, интерпретатор /lib64/ld-linux-x86-64.so.2'],
['x86_64-linux-musl', 'статически слинкован'],
],
},
{
kind: 'p',
text: 'Первая и третья строки ведут себя одинаково, а средняя — нет. Статическая линковка без интерпретатора — это сборка musl; сборка glibc несёт динамический загрузчик в заголовках программы. Цель без суффикса оказывается рядом с musl, и единственный триплет цели, встречающийся строкой где-либо в бинарнике без суффикса, — x86_64-linux-musl: Zig записывает разрешённый триплет, а не тот, что вы набрали.',
},
{
kind: 'p',
text: 'Может ли эта разница дотянуться до реального файла — вопрос к стандартной библиотеке. В использованной здесь установке 0.16.0 в std/c.zig одиннадцать вызовов isGnu() или isMusl():',
},
{
kind: 'code',
text: 'grep -cE \'\\.isGnu\\(\\)|\\.isMusl\\(\\)\' "$(dirname "$(readlink -f "$(which zig)")")/../lib/zig/std/c.zig"\n11',
},
{ kind: 'h', text: 'Что эта проверка поймать не могла' },
{
kind: 'p',
text: 'Файл, который дотягивается до одного из этих переключателей со стороны musl, чисто компилируется под x86_64-linux и падает под x86_64-linux-gnu. Задача была бы зелёной, а настоящая сборка — красной, и это ровно тот отказ, ради предотвращения которого задачу и заводили, сдвинутый на слой ниже. Проверка, написанная против платформенно-слепой верификации, сама верифицировала на платформе, которую никто не поставляет.',
},
{
kind: 'p',
text: 'Исправление — один суффикс. Теперь зафиксирована x86_64-linux-gnu, а измерение вписано в комментарий, чтобы три символа не читались как украшение тем, кто будет править строку следующим. Ложное утверждение исправлено на месте, а не удалено: «здесь была ошибка, и вот почему» переживает позднего читателя лучше, чем молчание.',
},
{ kind: 'h', text: 'Одно утверждение из pull request не воспроизводится' },
{
kind: 'p',
text: 'В pull request сборка с суффиксом musl названа «побайтово идентичной» сборке без суффикса. Так это не проверяется, и причина ценнее самого утверждения. Три сборки одной и той же цели, в три отдельные директории с одним и тем же именем вывода, дают три разных бинарника:',
},
{
kind: 'table',
head: ['сборка -target x86_64-linux', 'размер (байт)', 'префикс sha256'],
rows: [
['первая', '12 731 133', '468ce3a1'],
['вторая', '12 731 154', '6998eff5'],
['третья', '12 731 133', '1d387f61'],
],
},
{
kind: 'p',
text: 'Три хеша, два размера — от одной команды, запущенной трижды. Значит, хеш здесь вообще не различает цели: он не отличает цель даже от неё самой, и размер тоже. Этот контроль переживают линковка, о которой сообщает file(1), и разрешённый триплет, записанный в бинарнике. Доказательство — эти двое; хешем оно не было никогда.',
},
{
kind: 'p',
text: 'Это не ослабляет исправление. Оно заменяет убедительно звучащее подкрепление более слабым, но настоящим, — в ту сторону, которая ничего не стоит потом.',
},
{ kind: 'h', text: 'Три коммита, семьдесят минут' },
{
kind: 'p',
text: 'У файла workflow ровно три коммита, и два из них — исправления первого:',
},
{
kind: 'table',
head: ['PR', 'смержен (UTC)', 'что было не так'],
rows: [
['#746', '2026-09-05 18:38:29', 'задача заведена'],
['#747', '2026-09-05 18:57:37', 'её предикат проверял тип, а не точку входа'],
['#749', '2026-09-05 19:48:52', 'её цель разрешалась в musl, а не в glibc'],
],
},
{
kind: 'p',
text: 'Семьдесят минут двадцать три секунды от заведения до второго исправления. Ни один из дефектов не был виден в YAML; оба нашлись, когда тело шага извлекли и запустили дословно на дереве. Чтение проверки говорит, что она должна была проверять. Запуск говорит, что она проверяет.',
},
{ kind: 'h', text: 'Что здесь не установлено' },
{
kind: 'p',
text: 'Ни один файл в этом дереве не был показан зависящим от символа, специфичного для musl. Находка в том, что проверка не смогла бы такой файл поймать, а не в том, что он есть. Разрешение цели измерено на одном хосте в режиме кросс-компиляции, а не на раннере; поведение раннера предполагается таким же, потому что он забирает ту же версию Zig. И задача компилирует файлы — она их не запускает. Файл, слинкованный с нужным libc, тем самым ещё не показан работающим.',
},
]
34 changes: 34 additions & 0 deletions apps/website/src/data/blog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@ import type { PostMeta } from './types'

/** Индекс блога: список и метаданные без тяжёлых тел публикаций. */
export const postsIndex: PostMeta[] = [
{
slug: 'a-linux-target-with-no-abi-is-musl',
title: 'A Linux target with no ABI suffix is musl, and a CI gate had been trusting it',
summary: '[measured] Zig resolves -target x86_64-linux to musl. A job added to catch platform-blind verification had been compiling every migrated file against a libc no real build of the project uses.',
date: '2026-09-06',
readingMinutes: 4,
tags: ['Zig', 'CI', 'Toolchain', 'Reproducibility'],
receipts: [
{ label: '#749 — spell out the ABI; the 0.16 gate was compiling against musl · MERGED 2026-09-05', href: 'https://github.com/gHashTag/trinity-fpga/pull/749' },
{ label: '#747 — the same job\'s predicate tested the type, not the entry point · MERGED 2026-09-05', href: 'https://github.com/gHashTag/trinity-fpga/pull/747' },
{ label: '#746 — the commit that introduced the job · MERGED 2026-09-05', href: 'https://github.com/gHashTag/trinity-fpga/pull/746' },
{ label: 'The workflow at merge commit f32467d4f, with the measurement in the comment', href: 'https://github.com/gHashTag/trinity-fpga/blob/f32467d4ff6ec7e1b244049e651a65c09c1d0f77/.github/workflows/zig-0-16-migrated.yml' },
{ label: 'The "Zig 0.16 migrated files" run on the merge commit — success', href: 'https://github.com/gHashTag/trinity-fpga/actions/runs/33988238073' },
],
openQuestions: [
'No file in this tree was shown to depend on a musl-only symbol. What is established is that the gate could not have caught one, not that one exists.',
'The target resolution was measured on one host (macOS aarch64, Zig 0.16.0) cross-compiling. The runner is inferred to resolve the same way because it fetches the same Zig version; that was not re-measured on the runner.',
'The eleven isGnu()/isMusl() call sites are a count in std/c.zig in one local 0.16.0 install. It is not a measure of how many of them any file in this project reaches.',
'The pull request\'s "byte-identical" claim is not established in either direction: this build is not reproducible run to run on this host, so neither hash nor size can discriminate a target from itself.',
'The job compiles files; it does not run them. Linking against the right libc is not evidence that anything works.',
],
published: true,
ru: {
title: 'Linux-цель без суффикса ABI — это musl, и CI-проверка ей доверяла',
summary: '[измерено] Zig разрешает -target x86_64-linux в musl. Задача, заведённая ловить платформенно-слепую верификацию, компилировала каждый переведённый файл против libc, который не использует ни одна настоящая сборка проекта.',
openQuestions: [
'Ни один файл в этом дереве не показан зависящим от символа, специфичного для musl. Установлено, что проверка не смогла бы такой файл поймать, а не то, что он существует.',
'Разрешение цели измерено на одном хосте (macOS aarch64, Zig 0.16.0) в режиме кросс-компиляции. Предполагается, что раннер разрешает так же, поскольку забирает ту же версию Zig; на раннере это не перепроверялось.',
'Одиннадцать вызовов isGnu()/isMusl() — это подсчёт в std/c.zig одной локальной установки 0.16.0. Это не мера того, до скольких из них дотягивается хоть один файл проекта.',
'Утверждение pull request о «побайтовой идентичности» не установлено ни в одну сторону: сборка не воспроизводима от запуска к запуску на этом хосте, поэтому ни хеш, ни размер не отличают цель даже от неё самой.',
'Задача компилирует файлы, но не запускает их. Линковка с нужным libc не является свидетельством того, что что-то работает.',
],
},
},
{
slug: 'the-only-stable-speed-belonged-to-the-tool',
title: 'The only stable speed belonged to the tool, not the cable',
Expand Down
Loading
Loading