Skip to content

chore(release): одна платформа — один ассет - #155

Merged
zeegin merged 5 commits into
masterfrom
release/drop-direct-binaries
Sep 20, 2026
Merged

zeegin merged 5 commits into
masterfrom
release/drop-direct-binaries

Conversation

@zeegin

@zeegin zeegin commented Sep 17, 2026

Copy link
Copy Markdown
Member

Почему

Каждый выпуск выкладывал одну и ту же сборку дважды:

Ассет Что это
v8-runner-macos-aarch64.tar.gz архив с бинарником внутри
v8-runner-darwin-arm64 тот же бинарник, байт в байт, без архива

Скрипт это равенство сам же и проверял (binary != (dist / direct_name).read_bytes() → ошибка). То есть два имени на одну платформу, в двух разных традициях именования: macos/aarch64 по триплету Rust и darwin/arm64 в стиле Go. Плюс прямые бинарники были у трёх платформ из четырёх, а у Intel-мака — нет, и со стороны это неотличимо от забытой сборки.

Ровно этот вопрос и возник на v0.11.0: «а что из этого что?».

Что меняется

Остаются архивы, по одному на платформу. Релиз ужимается с 10 ассетов до 7, полезная нагрузка — с ~41 МБ до ~13. Аттестуемых артефактов становится 5 вместо 8.

Удалено: unica_asset_name из матрицы, три шага workflow (Prepare / Attest / Upload direct Unica asset), таблица DIRECT_ASSETS, подкоманда prepare-direct, перекрёстная сверка сумм между формами и роль direct-binary в манифесте.

Что пришлось переделать, а не удалить

audit-native скачивал черновик релиза и запускал именно прямой бинарник на каждой ОС — это доказательство, что опубликованный файл действительно стартует. Просто выкинуть его значило бы потерять проверку.

Вместо этого добавлена подкоманда extract-binary: она достаёт бинарник из архива тем же кодом, который архив проверяет (_archive_binary с канонизацией имён и сверкой вложенных LICENSE/FORK_NOTICE). Аудит запускает ровно тот файл, чью сумму несёт манифест, — и одинаково на всех трёх системах, без tar на одной и 7z на другой.

Побочный выигрыш: матрица аудита выросла с трёх платформ до четырёх. Intel-мак нативно не проверялся вообще — теперь проверяется.

Отдельная сверка сумм между формами больше не нужна по построению: форма одна.

Описание релиза

Тело релиза генерируется на каждый выпуск из литерала в release.yml. Туда добавлена таблица «архив → система → процессор» и подсказка про uname -m, потому что aarch64 против x86_64 — ровно то место, где спотыкаются. Там же названы удалённые имена: тот, кто пойдёт искать v8-runner-darwin-arm64, должен узнать, что его нет и почему, а не решить, что сборка сломалась.

Защита от повторения

test_a_platform_is_published_in_one_form_only падает, если имя возвращается в workflow, если таблица возвращается в скрипт или если платформа появляется в аудите дважды. Проверено пробой в обе стороны. Упоминание удалённых имён в теле описания примета разрешает намеренно.

test_extract_binary_yields_exactly_the_archived_binary закрепляет то, что заменило перекрёстную сверку: извлечённый файл равен архивному для каждой из четырёх целей, а незнакомая цель — названный отказ.

Проверки

  • python3 tests/release_governance.py — 18 passed
  • python3 tests/release_assets.py — 11 passed (CI гоняет оба файла, ci.yml:174-175)
  • YAML разбирается; матрицы сборки и аудита покрывают одни и те же четыре цели
  • Новый путь прогнан на настоящем архиве, собранном по раскладке из workflow: извлечение, права, запуск, сверка версии и Mach-O — проходит

Rust не затронут.

Что надо знать ревьюеру

Версия не поднята. v0.11.0 уже выпущена с обоими наборами, менять её задним числом нельзя — изменение вступит в силу с 0.12.0.

Потребителя проверяли. У Unica есть unica-bootstrap с зашитым releases/download/ этого репозитория и контрактным тестом, сопоставляющим платформу с именем прямого бинарника. По словам владельца, ни одна публичная Unica ничего из форка не забирает, поэтому согласование не требуется. Стоит знать, что у Unica уже есть extract_verified_tar_gz и application/gzip в допустимых формах доставки, то есть переезд на архив для неё — правка манифеста, а не новая машинерия.

Каждый выпуск выкладывал одну и ту же сборку дважды: архивом и голым бинарником
под другим именем. `v8-runner-darwin-arm64` — байт в байт тот же файл, что
`v8-runner-macos-aarch64/v8-runner` внутри архива; скрипт это равенство сам же и
проверял. Два имени на одну платформу, три из четырёх платформ с прямым
бинарником и четвёртая без — и на вопрос «что из этого что» приходилось отвечать
словами.

Остаются архивы, по одному на платформу. Полезная нагрузка релиза падает с
десяти ассетов до семи.

Нативный аудит не потерян, а переделан. Он запускал именно прямой бинарник,
доказывая, что опубликованное стартует; теперь бинарник достаётся из архива тем
же кодом, который архив проверяет, — значит запускается ровно тот файл, чью сумму
несёт манифест. Отдельной сверки сумм между формами больше не нужно: форма одна.
Заодно аудит вырос с трёх платформ до четырёх — Intel-мак нативно не проверялся
вовсе.

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

Защита от повторения: `test_a_platform_is_published_in_one_form_only` падает и
когда имя возвращается в workflow, и когда таблица возвращается в скрипт, и когда
платформа появляется в аудите дважды. Обе стороны проверены пробой.
Удаляя шаг выгрузки прямого ассета, я срезал текст до следующего `- name:` — а он
оказался уже внутри следующей работы. Вместе с шагом ушёл заголовок `publish`, её
пять шагов оказались внутри матричной сборки, а `audit-native` и `audit-draft`
стали ссылаться на работу, которой нет. GitHub такой файл не запускает вовсе:
релиз было не выпустить.

Оба набора тестов при этом оставались зелёными: они проверяли подстроки, а строка
`needs: [publish, audit-native]` никуда не делась — делась работа.

Поэтому вместе с возвратом заголовка появляется примета
`test_release_workflow_is_a_workflow_github_will_start`: она разбирает YAML,
сверяет состав работ, проверяет, что каждая зависимость разрешается и у каждой
работы есть шаги, и что публикует только `publish` — у матричной сборки прав на
запись нет, и съехавший в неё шаг публикации выполнялся бы по разу на платформу.
Проба: снятый заголовок роняет её.

Заодно по замечаниям ревьюера:

- имя извлечённого бинарника называет сам скрипт, а не матрица аудита: то же
  соответствие «цель — имя файла» больше не живёт в двух местах, и `.exe` на
  Windows берётся из описания архива;
- тест на извлечение проверяет не только байты, но и право на запуск — прежнюю
  проверку `chmod` унесло вместе с шагом, а без неё три из четырёх платформ
  ломались бы молча;
- состав архива описан точно: внутри ещё README и `examples/`, а на Windows
  бинарник называется иначе;
- в README убрана фраза про неизменные имена бинарников — рядом с абзацем о том,
  что бинарников больше нет, она противоречила сама себе.
`import yaml` уронил обе работы Happy Path: PyYAML на раннерах нет, а `pip install`
в CI нет ни одного — питон в этом репозитории намеренно живёт на стандартной
библиотеке, и реестр разбирает своё front matter сам по той же причине.

Структура рабочего процесса теперь читается по отступам: имена работ, их
зависимости и наличие шагов. Разбор сверен с PyYAML — состав работ и зависимости
совпадают. Проба прежняя: снятый заголовок `publish` роняет примету.
После вливания гейта рабочих процессов разрешимость `needs` проверяется по всем
файлам сразу. Здесь остаётся то, чего та проверка не видит: шаги публикации живут
в своей работе, у матричной сборки прав на запись нет, и ни один из них туда не
съехал — иначе они выполнялись бы по разу на платформу, затирая друг другу `dist`.
@zeegin
zeegin merged commit f1f2c36 into master Sep 20, 2026
5 checks passed
@zeegin
zeegin deleted the release/drop-direct-binaries branch September 20, 2026 10:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant