mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 06:59:46 +00:00
ci(validate): мутанты по диффу — только по явному запросу, не на кандидате беты и не в full (#601)
`mutantsRequested` отвечает true лишь на PR и `workflow_dispatch mutants=true` (конвейер ревью, слияние кандидата). Трейлер `Release:` и `full=true` включают тяжёлые гейты — смоки, golden, performance_smoke — но не мутантов: к бете каждая задача прогнана ими на ревью и на слитом после ребейза кандидате, ночь покрыта полным реестром (mutation-gate.yml, #513), а ручной полный прогон ради артефакта эталонов и приёмка эталонов с трейлером на ветке задачи платили шестью job впустую. `schedule` мутантов тоже не запрашивает. Политика release в ci-proof — `mutants: false`: иначе proof кандидата беты без запрошенных mutant-jobs объявлялся бы stale. review и merge по-прежнему требуют шесть исполненных job (#541). Тесты: #510 AC1 переписан под новый список, ci-proof — release без мутантов green, лёгкий stale, review/merge без запроса stale. Мутанты протокола: `mutants-run-on-every-push` перепривязан, новые `mutants-run-on-beta-candidate`, `mutants-run-on-full-dispatch`, `release-proof-demands-mutant-jobs`. PROCESS.md §10.4, AGENTS.md, docs/TESTING.md, комментарии workflow. Issue: #601 User-Visible: no
This commit is contained in:
@@ -18,10 +18,15 @@ export const CI_PROOF_STATES = Object.freeze([
|
||||
'green', 'missing', 'pending', 'cancelled', 'stale', 'failed',
|
||||
]);
|
||||
|
||||
// Мутанты по диффу — доказательство для ревью и слияния (#510): без шести
|
||||
// исполненных mutant-jobs ни то ни другое не разрешается. Релиз их не
|
||||
// требует (#601): к кандидату беты каждая задача прогнана ими на ревью и на
|
||||
// слитом кандидате, а `Release:` мутантов больше не запрашивает — политика
|
||||
// с `mutants: true` объявляла бы каждый кандидат беты `stale`.
|
||||
export const CI_PROOF_POLICIES = Object.freeze({
|
||||
review: Object.freeze({ name: 'review', full: false, mutants: true }),
|
||||
merge: Object.freeze({ name: 'merge', full: false, mutants: true }),
|
||||
release: Object.freeze({ name: 'release', full: true, mutants: true }),
|
||||
release: Object.freeze({ name: 'release', full: true, mutants: false }),
|
||||
});
|
||||
|
||||
const JOB_RULES = Object.freeze({
|
||||
|
||||
@@ -96,17 +96,25 @@ export function heavyGatesRequested({ eventName, headMessage, fullInput } = {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Нужны ли мутанты по диффу (#510). За 08–09.09 они съели 86 % job-минут
|
||||
* Validate, потому что бежали на каждом промежуточном пуше и отменялись
|
||||
* следующим. Место мутантов — кандидат: ревью-конвейер и слияние кандидата
|
||||
* запускают Validate по кнопке с `mutants=true`, ночной прогон и PR берут
|
||||
* полный набор, кандидат беты несёт трейлер `Release:`. Обычный push — нет.
|
||||
* Нужны ли мутанты по диффу (#510, сужено в #601). За 08–09.09 они съели 86 %
|
||||
* job-минут Validate, потому что бежали на каждом промежуточном пуше и
|
||||
* отменялись следующим. Место мутантов — кандидат ревью и кандидат слияния:
|
||||
* оба запускают Validate по кнопке с `mutants=true`; на PR Validate —
|
||||
* единственный сигнал, поэтому там тоже. Больше нигде: мутанты проверяют
|
||||
* тесты, а не продукт (#513), и к моменту беты каждая задача прогнана ими
|
||||
* дважды — на ревью и на слитом после ребейза кандидате. Трейлер `Release:`
|
||||
* и `full=true` включают тяжёлые гейты, но не мутантов — иначе ручной полный
|
||||
* прогон ради артефакта эталонов и приёмка эталонов с трейлером на ветке
|
||||
* задачи платили шестью job впустую (#601). Ночь — полный реестр
|
||||
* (mutation-gate.yml), не диффовое подмножество; `schedule` здесь тоже
|
||||
* не запрашивает, чтобы будущее расписание Validate не вернуло их молча.
|
||||
* CLI по-прежнему передаёт headMessage и fullInput вместе с остальным —
|
||||
* функция их не читает, и тест закрепляет, что они НЕ влияют на ответ.
|
||||
*/
|
||||
export function mutantsRequested({ eventName, headMessage, fullInput, mutantsInput } = {}) {
|
||||
export function mutantsRequested({ eventName, mutantsInput } = {}) {
|
||||
if (eventName === 'pull_request') return true;
|
||||
if (eventName === 'schedule') return true;
|
||||
if (eventName === 'workflow_dispatch') return String(fullInput) === 'true' || String(mutantsInput) === 'true';
|
||||
return hasReleaseTrailer(headMessage);
|
||||
if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9618,12 +9618,37 @@ const MUTANT_DEFINITIONS = [
|
||||
{
|
||||
id: 'mutants-run-on-every-push',
|
||||
guard: 'node --test --test-name-pattern="#510" test/classify-changes.test.mjs',
|
||||
because: 'mutants by diff belong to the review candidate, the PR, the nightly run and the beta '
|
||||
+ 'candidate — an ordinary push must not spend 3×8 runner minutes on them (#510 AC1)',
|
||||
because: 'mutants by diff belong to the review candidate, the merge candidate and the PR — an '
|
||||
+ 'ordinary push must not spend 3×8 runner minutes on them (#510 AC1, narrowed in #601)',
|
||||
patches: [{
|
||||
file: 'scripts/classify-changes.mjs',
|
||||
find: " if (eventName === 'workflow_dispatch') return String(fullInput) === 'true' || String(mutantsInput) === 'true';\n return hasReleaseTrailer(headMessage);\n}",
|
||||
replace: " if (eventName === 'workflow_dispatch') return String(fullInput) === 'true' || String(mutantsInput) === 'true';\n return true; // mutant: every push\n}",
|
||||
find: " if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return false;\n}",
|
||||
replace: " if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return true; // mutant: every push\n}",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'mutants-run-on-beta-candidate',
|
||||
guard: 'node --test --test-name-pattern="#601" test/classify-changes.test.mjs',
|
||||
because: 'the `Release:` trailer requests the heavy gates, not the diff mutants (#601 AC1): by the '
|
||||
+ 'beta candidate every issue has already been mutated on its review and merge candidates, and '
|
||||
+ 'the trailer also lands on class-D baseline commits inside task branches (f342ccce) — six '
|
||||
+ 'mutant jobs there prove nothing about tests that did not change',
|
||||
patches: [{
|
||||
file: 'scripts/classify-changes.mjs',
|
||||
find: "export function mutantsRequested({ eventName, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return false;\n}",
|
||||
replace: "export function mutantsRequested({ eventName, headMessage, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return hasReleaseTrailer(headMessage); // mutant: beta candidate\n}",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'mutants-run-on-full-dispatch',
|
||||
guard: 'node --test --test-name-pattern="#601" test/classify-changes.test.mjs',
|
||||
because: '`full=true` is the heavy set — smokes, golden, performance — and the nightly dispatch '
|
||||
+ '(#601 AC1). Tying the diff mutants to it made a manual full run for a golden artifact pay six '
|
||||
+ 'jobs the review conveyor then cancelled by concurrency, and duplicated the nightly registry',
|
||||
patches: [{
|
||||
file: 'scripts/classify-changes.mjs',
|
||||
find: "export function mutantsRequested({ eventName, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';",
|
||||
replace: "export function mutantsRequested({ eventName, fullInput, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(fullInput) === 'true' || String(mutantsInput) === 'true'; // mutant: full requests",
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -9637,6 +9662,18 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: " if (false && run.conclusion !== 'success')\n return result('failed', 'mutant'); // mutant: completed means green",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'release-proof-demands-mutant-jobs',
|
||||
guard: 'node --test --test-name-pattern="#601" test/ci-proof.test.mjs',
|
||||
because: 'with #601 the beta candidate no longer requests diff mutants, so a release policy that '
|
||||
+ 'still demands them marks every candidate proof stale and blocks publish-prerelease on a green '
|
||||
+ 'full Validate (#601 AC3); review and merge keep demanding them (#541)',
|
||||
patches: [{
|
||||
file: 'scripts/ci-proof.mjs',
|
||||
find: " release: Object.freeze({ name: 'release', full: true, mutants: false }),",
|
||||
replace: " release: Object.freeze({ name: 'release', full: true, mutants: true }), // mutant: release demands mutants",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'review-anchor-drops-issue-body',
|
||||
guard: 'node --test test/review-doc-guard.test.mjs',
|
||||
|
||||
Reference in New Issue
Block a user