fix(process): prepare and the usage step run pipeline scripts from one pinned dev snapshot (#765)

The body of _process.yml is read from dev (@dev, #623). After "Перейти на
ветку задачи" the working copy of job prepare is the task branch, and a
show/ship branch with a clean merge is not rebased before review: its
scripts/ may lag dev or be replaced. #749 fixed job integrate; prepare
still ran four control scripts from the material — the issue-body digest
for the anchor, --reuse of a green verdict (#499), validate-gate (#510)
and the spec-change check (#517). The material decided its own admission:
a branch whose review-doc-guard.mjs prints reuse=true merges without the
model. model_review took model-usage.mjs from the material too: a lagging
branch has none, and the publication silently wrote reason=missing.

Now prepare extracts one snapshot right after setup-node, before the
branch switch: `git rev-parse origin/dev` once and `git archive <sha>
scripts .github/workflows/validate.yml`, so the git fetch of the track and
rebase steps cannot mix versions. Every repo script of the job runs from
it via TOOLS — the track step and the rebase guard lose their own
extractions. The SHA goes out as job output tools_sha; the usage step of
model_review archives the same commit inside itself, so a snapshot failure
is a failure of the reporting step (continue-on-error), not of the stage.
The model session runs on that runner, so the usage line stays untrusted
input parsed strictly (#556, #737).

withMaterialAnchors is idempotent: a repeated call drops the separator the
previous call wrote instead of piling up `---` lines.

Tests: test/process-prepare-tools.test.mjs — the job contract (no step
calls scripts/ from the working copy, one pinned archive before the branch
switch, tools_sha reaches model_review) and the steps as they are, on real
bash and git: a branch behind dev without model-usage.mjs and with a
substituted review-doc-guard.mjs; the anchor digest, reuse and the spec
check come from dev, the usage line is data even after dev moved. Red on
the old workflow: all four. Harnesses of process-track, rebase-generated,
review-doc-guard and model-usage take the prepare snapshot. Three registry
mutants (reuse from the material, usage from moving dev, separators).

Canon: PROCESS.md §10.4 «Скрипты конвейера — из dev» covers prepare and
the usage step; «Расход модели» names it a pipeline step, not the reviewer's.

Issue: #765
User-Visible: no
This commit is contained in:
claude[bot]
2026-10-02 10:18:29 +03:00
parent ceee249210
commit 23f822475b
10 changed files with 462 additions and 49 deletions
+33
View File
@@ -14201,6 +14201,39 @@ const MUTANT_DEFINITIONS = [
replace: ' const key = `${spaceId}|`; // mutant: no content fingerprint\n',
}],
},
{
id: 'prepare-reuse-runs-material-script',
guard: 'node --test --test-name-pattern="#765 AC2" test/process-prepare-tools.test.mjs',
because: '#765: reuse зелёного вердикта (#499) решает скрипт снимка dev; скрипт рабочей копии — '
+ 'материал задачи, и подменённый review-doc-guard.mjs выдаёт себе reuse=true — слияние без модели',
patches: [{
file: '.github/workflows/_process.yml',
find: ' out=$(node "$TOOLS/scripts/review-doc-guard.mjs" --reuse --marker=CODE-REVIEW',
replace: ' out=$(node scripts/review-doc-guard.mjs --reuse --marker=CODE-REVIEW',
}],
},
{
id: 'usage-script-from-moving-dev',
guard: 'node --test --test-name-pattern="#765 AC3" test/process-prepare-tools.test.mjs',
because: '#765: расход снимается скриптом того коммита dev, что закрепила подготовка; архив '
+ 'подвижной origin/dev после сдвига dev исполняет другую версию — смесь версий в одном заходе',
patches: [{
file: '.github/workflows/_process.yml',
find: ' git archive "$TOOLS_SHA" scripts | tar -x -C "$tools"',
replace: ' git fetch -q origin dev; git archive origin/dev scripts | tar -x -C "$tools"',
}],
},
{
id: 'material-anchors-pile-separators',
guard: 'node --test --test-name-pattern="#765" test/review-doc-guard.test.mjs',
because: '#765: повторная приписка блока якорей снимает и свой разделитель; без этого каждый '
+ 'повтор копит ещё один `---` перед блоком',
patches: [{
file: 'scripts/review-doc-guard.mjs',
find: " ? body.slice(0, at).replace(/(?:\\s*\\n---)*\\s*$/, '')",
replace: " ? body.slice(0, at).replace(/\\s+$/, '')",
}],
},
];
const mutationCardSource = readFileSync(join(repoRoot, 'src/houseplan-card.ts'), 'utf8');
+8 -2
View File
@@ -388,11 +388,17 @@ export function parseSpecList(raw) {
.filter((item) => /^[0-9a-f]{40}$/.test(item.blob) && item.path);
}
/** Дописать или заменить блок якорей в тексте документа. */
/**
* Дописать или заменить блок якорей в тексте документа. Идемпотентно (#765):
* при замене снимается и разделитель `---`, который поставила прошлая
* приписка, — иначе каждый повтор копил бы ещё один перед блоком.
*/
export function withMaterialAnchors(text, anchors) {
const body = String(text ?? '');
const at = body.indexOf(ANCHOR_MARKER);
const head = at >= 0 ? body.slice(0, at).replace(/\s+$/, '') : body.replace(/\s+$/, '');
const head = at >= 0
? body.slice(0, at).replace(/(?:\s*\n---)*\s*$/, '')
: body.replace(/\s+$/, '');
return `${head}\n\n---\n\n${materialAnchorBlock(anchors)}`;
}