mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
fix(process): publish steps tell a GitHub push refusal from a moved branch (#723)
Two steps publish a commit and treated every failed push as a moved branch: the release review job (release-review.yml) retried three times with "dev went ahead", and the review document step (_process.yml) rebased and pushed again. A refusal by GitHub itself - a token without the workflow right, a branch rule, a hook - cannot be cured by a retry or a rebase, and the step never said what GitHub answered. Both pushes now keep stderr and hand it to the #705 classifier through the same CLI the rebase guard uses (merge-candidate.mjs --push-refusal). Only a stale lease (rejected / fetch first / stale info) keeps the old retry or rebase. Any other outcome stops the step at once, without retries: the log gets the git answer and the step summary gets the reason and the git answer, both passed through redactSecrets (token, credential URL, Authorization). The review document step takes the classifier from dev, as the rebase guard does: a task branch behind dev may not carry it. The summary text is written by the new --summary option (refusalSummary), not by a multi-line string in run:, and both commit messages are now built line by line into a file instead of a heredoc (PROCESS.md §10.4 item 4). release-review.yml is dispatch-only and is not mirrored to main. PROCESS.md names the rule next to the rebase guard; the #638 trailer witness in test/release-review.test.mjs follows the line-by-line message. test/publish-push-refusal.test.mjs runs both steps as they are with real bash and real git in temporary repositories; only the push transport is replaced: a moved branch is a real neighbour push, a GitHub refusal is a recorded stderr carrying a token, a credential URL and an Authorization header. On the old steps 9 of its 11 tests fail. Issue: #723 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -514,19 +514,57 @@ export function describePushRefusal(stderr, { ref, branch, candidate, stage = 'r
|
||||
return { refusal, comment };
|
||||
}
|
||||
|
||||
/**
|
||||
* #723: шаги публикации — документ ревью в ветку задачи (`_process.yml`) и
|
||||
* документ ревью релиза в `dev` (`release-review.yml`) — любой отказ push
|
||||
* считали сдвигом ветки и повторяли. Повтор лечит только устаревший lease;
|
||||
* отказ GitHub шаг останавливает, а причина и ответ git (уже без секретов)
|
||||
* ложатся в сводку шага. Текст — здесь, а не многострочной строкой в `run:`.
|
||||
*/
|
||||
const PUBLISHED = Object.freeze({
|
||||
'review-doc': 'Документ ревью',
|
||||
'release-review': 'Документ независимого ревью релиза',
|
||||
});
|
||||
|
||||
export function refusalSummary(refusal, { ref = '', stage = '' } = {}) {
|
||||
const what = PUBLISHED[stage] || 'Коммит';
|
||||
const why = refusal.kind === PUSH_REFUSAL.workflow
|
||||
? 'GitHub отклонил push по праву на workflow: у токена конвейера нет права создавать и менять `.github/workflows/`'
|
||||
: refusal.kind === PUSH_REFUSAL.remote
|
||||
? 'GitHub отклонил push сам (правило ветки, хук, сбой сервера)'
|
||||
: 'git push не удался, и это не отказ по lease (сеть, аутентификация)';
|
||||
// Ответ GitHub не должен открыть или закрыть блок кода сводки.
|
||||
const unfence = (text) => String(text || '').replace(/```/g, "'''");
|
||||
const files = (refusal.files || []).map((file) => `\`${file}\``).join(', ');
|
||||
const stderr = unfence(refusal.stderr);
|
||||
return [
|
||||
`### git push в \`${ref}\` отклонён: ${refusal.kind} (#723)`,
|
||||
'',
|
||||
`${what} не опубликован в \`${ref}\`. ${why}. Это не сдвиг \`${ref}\`: повтор и ребейз не помогут, шаг остановлен без повторов.`,
|
||||
...(refusal.reason ? ['', `Причина, которую назвал GitHub: «${unfence(refusal.reason)}».${files ? ` Файлы: ${files}.` : ''}`] : []),
|
||||
'',
|
||||
stderr ? `Ответ git:\n\n\`\`\`\n${stderr}\n\`\`\`` : 'git не прислал текста отказа.',
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* `--push-refusal=<файл со stderr git push>`: в stdout — одно слово исхода
|
||||
* (`stale`, `workflow`, `remote-rejected`, `unknown`), в stderr — ответ git
|
||||
* без секретов (журнал), с `--comment=<файл>` — комментарий для issue.
|
||||
* без секретов (журнал), с `--comment=<файл>` — комментарий для issue, с
|
||||
* `--summary=<файл>` (#723) — сводка шага об отказе, который повтор не лечит
|
||||
* (для `stale` не пишется: шаг повторяет).
|
||||
*/
|
||||
function pushRefusalMain() {
|
||||
const secrets = [process.env.TOKEN, process.env.HP_PROCESS_TOKEN, process.env.GH_TOKEN].filter(Boolean);
|
||||
const ref = arg('ref') || arg('branch');
|
||||
const stage = arg('stage') || 'rebase';
|
||||
const { refusal, comment } = describePushRefusal(readFileSync(arg('push-refusal'), 'utf8'), {
|
||||
ref: arg('ref') || arg('branch'), branch: arg('branch'), candidate: arg('candidate'),
|
||||
stage: arg('stage') || 'rebase', pipelineUrl: arg('run-url'), secrets,
|
||||
ref, branch: arg('branch'), candidate: arg('candidate'), stage, pipelineUrl: arg('run-url'), secrets,
|
||||
});
|
||||
console.error(`git push отклонён — ${refusal.kind}${refusal.reason ? ` (${refusal.reason})` : ''}:\n${refusal.stderr || '(stderr пуст)'}`);
|
||||
if (arg('comment') && comment) writeFileSync(arg('comment'), `${comment}\n`);
|
||||
if (arg('summary') && refusal.kind !== PUSH_REFUSAL.stale) appendFileSync(arg('summary'), refusalSummary(refusal, { ref, stage }));
|
||||
process.stdout.write(`${refusal.kind}\n`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user