process: preflight does not fail a task branch for foreign causes (#700)

11 of 85 returns in #600–#691 were the thin-workflow mirror check, and any
push could turn red because a foreign site behind a docs link was down.

- validate.yml preflight: on refs/heads/issue/* the workflow_sync mismatch
  is a warning in the summary, not a failed verdict; push to dev, the beta
  candidate and the release keep it red.
- On push to dev a mismatch opens one owner issue titled [workflow-sync]
  (or comments on the open one), like the nightly mutation gate (#472);
  preflight gets issues: write for that.
- check-docs --external=warn: external link failures become warnings; the
  docs step passes it on task branches only.

Canon: PROCESS.md §10.4 («Workflow из ветки по умолчанию»).

Issue: #700
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:
Claude
2026-09-29 01:22:14 +03:00
parent e1700757de
commit e45bc87c6d
7 changed files with 127 additions and 12 deletions
+25 -1
View File
@@ -440,7 +440,7 @@ test('смоки, golden и performance_smoke условны по heavy (#479)',
assert.match(text, /classify-changes\.mjs --heavy/);
assert.match(text, /workflow_dispatch:\n\s+inputs:\n\s+full:/);
// preflight: режим скриншотов считает тот же скрипт.
assert.match(text, /check-docs\.mjs --external --screenshots=\$mode/);
assert.match(text, /check-docs\.mjs "\$external" --screenshots=\$mode/);
});
test('ночной прогон — dispatch Validate на dev с full=true (#479)', () => {
@@ -613,3 +613,27 @@ test('#541: Validate всегда публикует proof точной попы
assert.equal(reuse.includes('lookup-only: true'), false,
'marker contents must be restored and verified, not reduced to a cache-hit bit');
});
test('#700: на ветке задачи зеркало workflow и внешние ссылки — предупреждение, на dev — красный и issue', () => {
const workflow = read('validate.yml');
const preflight = workflow.slice(workflow.indexOf('\n preflight:\n'), workflow.indexOf('\n changes:\n'));
const docs = preflight.slice(preflight.indexOf('id: docs'), preflight.indexOf('id: reviews_index'));
assert.match(docs, /case "\$REF" in refs\/heads\/issue\/\*\) external=--external=warn ;; esac/);
assert.match(docs, /REF: \$\{\{ github\.ref \}\}/);
const verdict = preflight.slice(preflight.indexOf('- name: Вердикт предполётных проверок'));
assert.match(verdict, /case "\$REF" in refs\/heads\/issue\/\*\) task_branch=true ;; esac/);
assert.match(verdict, /if \[ "\$task_branch" = "true" \]; then\n\s+advise "тонкие вызывающие workflow в main и dev" "\$WORKFLOW_SYNC"\n\s+else\n\s+check "тонкие вызывающие workflow в main и dev"/);
assert.doesNotMatch(verdict.slice(verdict.indexOf('advise() {'), verdict.indexOf('task_branch=false')), /fail=1/,
'предупреждение не красит вердикт');
const issue = preflight.slice(preflight.indexOf('- name: "Расхождение зеркала на dev — issue владельцу"'));
assert.match(issue, /if: github\.event_name == 'push' && github\.ref == 'refs\/heads\/dev' && steps\.workflow_sync\.outcome == 'failure'/);
assert.match(issue, /gh issue list --repo "\$REPO" --state open --search/, 'одно issue, а не одно на каждый push');
assert.match(preflight, /permissions:\n\s+contents: read\n\s+actions: read\n\s+issues: write/);
});
test('#700: check-docs --external=warn сводит внешние отказы в предупреждения', () => {
const source = read('../../scripts/check-docs.mjs');
assert.match(source, /const EXTERNAL_WARN = process\.argv\.includes\('--external=warn'\);/);
assert.match(source, /const externalErrors = EXTERNAL_WARN \? warnings : errors;/);
assert.equal((source.match(/externalErrors\.push\(/g) || []).length, 2, 'оба вида внешнего отказа');
});