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})`);
}
}
}