mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 06:38:57 +00:00
ci: единый staged→tested→published путь установочных ассетов (#540)
`release-zip.yml` выкладывал `houseplan.zip` в ту же секунду, когда релиз становился публичным — до Validate, Full Performance и E2E; `release.yml` параллельно пересобирал `houseplan-card.js`, а E2E требовал публичного ZIP, чтобы вообще начаться. Публикаторов было четыре, порядок — ни одного. Теперь публикатор стабильных один — `release.yml`: закрепить SHA → релиз в черновике (опубликованный руками немедленно возвращается в черновик) → гейты на SHA (трейлер `Release: <tag>`, контракт `--stable`, Validate, Full Performance, E2E на коммите-кандидате через tarball codeload) → одна сборка, `git archive` ZIP из того же дерева, `SHA256SUMS` → загрузка в черновик → публикация → скачать публичное и сверить с паспортом → анонс. Dispatch на публичный тег — ремонт: догружается только недостающее, расходящийся хеш — отказ. Беты кладут тот же паспорт; локальный публикатор больше не ждёт републикаторов — их нет. - `.github/workflows/release-zip.yml` удалён - `scripts/release-assets.mjs` — паспорт ассетов (`sums`/`check`), чистые функции под юнитами - `scripts/e2e-gate.mjs --ref=<sha>` — под тестом кандидат, `--tag` только для выбора `upgrade_from` - `scripts/release-contract.mjs --stable` - мутанты: независимый публикатор, снятая зависимость от гейта, релиз без возврата в черновик, `--clobber` в ремонте, E2E на теге, слепой паспорт Issue: #540 User-Visible: no
This commit is contained in:
@@ -7782,8 +7782,70 @@ const MUTANT_DEFINITIONS = [
|
||||
+ 'ответ». Без неё анонс уходит при красном гейте, то есть ровно то, что случилось',
|
||||
patches: [{
|
||||
file: '.github/workflows/release.yml',
|
||||
find: ' name: Оповещение о релизе после выкладки\n needs: build',
|
||||
replace: ' name: Оповещение о релизе после выкладки\n if: always()',
|
||||
find: ' name: Оповещение о релизе после выкладки\n needs: [candidate, publish]\n'
|
||||
+ " if: ${{ needs.publish.outputs.newly_published == 'true' }}",
|
||||
replace: ' name: Оповещение о релизе после выкладки\n needs: [candidate]\n if: always()',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'asset-upload-stops-needing-the-gate',
|
||||
guard: 'node --test test/release-workflow.test.mjs',
|
||||
because: '#540: единственный публикатор ассетов ценен ровно тем, что стоит ЗА гейтом. '
|
||||
+ 'Снятая зависимость — это `release-zip.yml` под другим именем: ассеты уходят в '
|
||||
+ 'ту же минуту, когда Validate, Full Performance и E2E ещё идут или уже красные',
|
||||
patches: [{
|
||||
file: '.github/workflows/release.yml',
|
||||
find: ' stage:\n name: "Сборка: ассеты, SHA256SUMS и загрузка в черновик"\n needs: [candidate, gate]',
|
||||
replace: ' stage:\n name: "Сборка: ассеты, SHA256SUMS и загрузка в черновик"\n needs: [candidate]',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'hand-published-release-stays-public-during-the-gates',
|
||||
guard: 'node --test test/release-workflow.test.mjs',
|
||||
because: '#540: fail-closed держится на одном шаге — релиз, опубликованный руками, '
|
||||
+ 'немедленно возвращается в черновик. Без него всё время гейтов (час и больше) '
|
||||
+ 'снаружи висит публичный релиз с непроверенными или отсутствующими ассетами — '
|
||||
+ 'состояние v1.75.0 12.09',
|
||||
patches: [{
|
||||
file: '.github/workflows/release.yml',
|
||||
find: ' gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft\n',
|
||||
replace: ' true\n',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'repair-clobbers-the-public-asset',
|
||||
guard: 'node --test test/release-workflow.test.mjs',
|
||||
because: '#540: ремонт догружает только недостающее. `--clobber` на публичном релизе '
|
||||
+ 'подменяет байты, которые кто-то уже скачал и установил, — молча и без следа; '
|
||||
+ 'расхождение хеша обязано быть отказом, а не перезаписью',
|
||||
patches: [{
|
||||
file: '.github/workflows/release.yml',
|
||||
find: ' gh release upload "$TAG" $missing --repo "$GITHUB_REPOSITORY"\n',
|
||||
replace: ' gh release upload "$TAG" $missing --repo "$GITHUB_REPOSITORY" --clobber\n',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'e2e-gate-tests-the-tag-instead-of-the-candidate',
|
||||
guard: 'node --test test/e2e-gate.test.mjs',
|
||||
because: '#540: E2E на теге качает `houseplan.zip` из публичного релиза — то есть '
|
||||
+ 'требует публикации ДО проверки, и цикл «релиз должен быть публичным, чтобы его '
|
||||
+ 'проверить» возвращается. Под тестом обязан быть SHA кандидата',
|
||||
patches: [{
|
||||
file: 'scripts/e2e-gate.mjs',
|
||||
find: ' await ops.dispatch(ref, previousStable(await ops.releases(), tag));',
|
||||
replace: ' await ops.dispatch(tag, previousStable(await ops.releases(), tag));',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'passport-accepts-a-different-hash',
|
||||
guard: 'node --test test/release-assets.test.mjs',
|
||||
because: '#540: паспорт ассетов существует ради одного сравнения — публичные байты '
|
||||
+ 'равны проверенным. Ослеплённое сравнение делает SHA256SUMS украшением: релиз с '
|
||||
+ 'подменённым ZIP проходит сверку зелёным',
|
||||
patches: [{
|
||||
file: 'scripts/release-assets.mjs',
|
||||
find: ' else if (actual[name] !== expected[name]) mismatched.push(name);',
|
||||
replace: ' else if (false) mismatched.push(name);',
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -8812,8 +8874,8 @@ const MUTANT_DEFINITIONS = [
|
||||
+ 'a failed run as green ships the assets the run just rejected (#514 AC1)',
|
||||
patches: [{
|
||||
file: 'scripts/e2e-gate.mjs',
|
||||
find: " if (run.conclusion === 'success') return { result: 'green', url: run.url, note: `E2E на ${tag} зелёный` };",
|
||||
replace: " return { result: 'green', url: run.url, note: `E2E на ${tag} зелёный` }; // mutant: completed means green",
|
||||
find: " if (run.conclusion === 'success') return { result: 'green', url: run.url, note: `E2E на ${ref} зелёный` };",
|
||||
replace: " return { result: 'green', url: run.url, note: `E2E на ${ref} зелёный` }; // mutant: completed means green",
|
||||
}],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user