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
+9 -3
View File
@@ -9,7 +9,12 @@ import { freshnessSink, screenshotsMode } from './docs-freshness.mjs';
import { guideParityErrors } from './user-guide-parity.mjs';
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const EXTERNAL = process.argv.includes('--external');
// `--external` — внешние ссылки судятся ошибкой; `--external=warn` — только
// предупреждением (#700): упавший чужой сайт не красит ветку задачи, к чьему
// изменению он отношения не имеет. Блокируют внешние ссылки push в dev,
// кандидат беты и релиз — там их чинит релиз-менеджер.
const EXTERNAL_WARN = process.argv.includes('--external=warn');
const EXTERNAL = EXTERNAL_WARN || process.argv.includes('--external');
const PUBLIC_DOCS = [
'README.md', 'README.ru.md', 'docs/USER-GUIDE.md', 'docs/USER-GUIDE.ru.md',
'docs/TOUCH-SUPPORT.md', 'docs/DECOR-EDITOR.md', 'docs/VACUUM.md',
@@ -233,6 +238,7 @@ if (!existsSync(manifestPath)) {
}
if (EXTERNAL) {
const externalErrors = EXTERNAL_WARN ? warnings : errors;
const allowlist = JSON.parse(canonicalText(resolve(ROOT, 'docs/external-link-allowlist.json')));
const transientHosts = new Set(allowlist.transientHosts || []);
for (const href of [...externalUrls].sort()) {
@@ -249,10 +255,10 @@ if (EXTERNAL) {
continue;
}
}
errors.push(`external link returned ${response.status}: ${href}`);
externalErrors.push(`external link returned ${response.status}: ${href}`);
} catch (error) {
if (transientHosts.has(url.hostname)) warnings.push(`transient external failure: ${href} (${error.message})`);
else errors.push(`external link failed: ${href} (${error.message})`);
else externalErrors.push(`external link failed: ${href} (${error.message})`);
}
}
}
+23
View File
@@ -12706,6 +12706,29 @@ const MUTANT_DEFINITIONS = [
replace: " if (d.startsWith('M') || d.length >= 0) return d;",
}],
},
// #700: предполёт не красит ветку задачи чужими причинами.
{
id: 'task-branch-workflow-sync-red-again',
guard: 'node --test --test-name-pattern="#700" test/validate-workflow.test.mjs',
because: '#700: a thin workflow mirror mismatch has nothing to do with the task branch; 11 of 85 '
+ 'returns in #600–#691 came from it — on issue/* it is a warning, on dev a red preflight',
patches: [{
file: '.github/workflows/validate.yml',
find: ' advise "тонкие вызывающие workflow в main и dev" "$WORKFLOW_SYNC"',
replace: ' check "тонкие вызывающие workflow в main и dev" "$WORKFLOW_SYNC"',
}],
},
{
id: 'external-link-warn-mode-ignored',
guard: 'node --test --test-name-pattern="#700: check-docs" test/validate-workflow.test.mjs',
because: '#700: a foreign site that is down must not turn a task branch red; --external=warn '
+ 'routes external failures to warnings',
patches: [{
file: 'scripts/check-docs.mjs',
find: ' const externalErrors = EXTERNAL_WARN ? warnings : errors;',
replace: ' const externalErrors = errors; // mutant: warn mode ignored',
}],
},
{
id: 'screenshot-freshness-never-strict',
guard: 'node --test --test-name-pattern="#586" test/classify-changes.test.mjs',