fix: the screenshot witness floor comes from the set, not from survivors

Тот же дефект, что #408 у golden, и в моём же коде из #401. Порог свидетелей
считался от числа кадров, уцелевших на диске: rm docs/images/*.png плюс
объявить все десять через --expect-change — уцелевших ноль, порог ноль,
причины никто не спрашивает, а в манифесте остаётся {"witnesses":0,"floor":0}
без единого слова о произошедшем. Щель была описана в комментарии над самой
функцией и оставлена открытой.

Порог теперь от набора сценариев, свидетели — из тех, с кем есть что
сравнить. Разделение принципиальное: удаление кадров лишает доказательств, но
не должно снижать планку. Первичная съёмка идёт через --no-witnesses
--reason, как теперь и в golden.

Отдельного параметра размера, как в golden, здесь не нужно, и это не
небрежность: `ids` и есть набор — docs-accept.mjs передаёт DOC_SCREENSHOTS, а
verifyDocsCandidate до того отказывает, если набор сцен в кандидате не совпал
с ожидаемым. Пустой ids — отказ, а не ноль.

Попутно Low из #405: повторная приёмка неизменённого набора затирала
acceptance.declared пустым списком. След приёмки отвечает на вопрос «когда
эти пиксели приняли и что тогда объявляли», а обновление отпечатка пикселей
не меняет — значит и стирать ответ не должно. Прежний след сохраняется и
помечается lastWriteWasFingerprintOnly.

Заодно отказ по свидетелям теперь возвращает сами числа: вызывающий печатает
свой вердикт, и сочинять их заново ему не из чего.

Issue: #409
User-Visible: no
This commit is contained in:
Matysh
2026-09-01 17:54:48 +03:00
parent cd0ecf4db2
commit 8119c523fa
3 changed files with 108 additions and 14 deletions
+35 -8
View File
@@ -31,18 +31,30 @@
*/
/**
* Порог свидетелей. Формула та же, что у golden (`goldenWitnessFloor`), и это
* намеренно: два набора картинок в одном репозитории не должны требовать от
* человека помнить два разных правила.
* Порог свидетелей — от размера НАБОРА сценариев. Формула та же, что у golden
* (`goldenWitnessFloor`), и это намеренно: два набора картинок в одном
* репозитории не должны требовать от человека помнить два разных правила.
*
* Для десяти сцен порог равен одной. Этого достаточно, потому что основную
* работу делает не порог, а требование «все необъявленные совпали»: расхождение
* среды не бывает точечным. Порог закрывает единственную оставшуюся щель —
* попытку объявить изменёнными все кадры разом, когда свидетелей не остаётся
* вовсе и подтвердить среду становится нечем.
*
* Первая редакция (#401) считала порог от числа кадров, УЦЕЛЕВШИХ на диске, и
* той самой щелью и обходилась: `rm docs/images/*.png` + объявить все десять
* через `--expect-change` — уцелевших ноль, порог ноль, причины никто не
* спрашивает, а в манифесте остаётся `{"witnesses":0,"floor":0}` без единого
* слова о том, что произошло. Щель была описана в этом самом комментарии
* строкой выше и оставлена открытой (#409, близнец golden-дефекта #408).
*
* Отсутствие закоммиченных кадров — не смягчающее обстоятельство. Свидетелем
* такая сцена быть не может, это правда; но невозможность доказать среду не
* отменяет требования, а требует сказать это вслух — `--no-witnesses
* --reason="…"`, и причина уезжает в манифест.
*/
export const docsWitnessFloor = (committedCount) => (committedCount > 0
? Math.min(10, Math.ceil(committedCount * 0.1))
export const docsWitnessFloor = (sceneCount) => (sceneCount > 0
? Math.min(10, Math.ceil(sceneCount * 0.1))
: 0);
/** Объявленные имена, которых нет в наборе сценариев. */
@@ -104,13 +116,28 @@ export function docsAcceptancePlan({
+ ' принимать нельзя: растеризация задевает все кадры с текстом сразу' };
}
// Порог — от набора сценариев, свидетели — из тех, с кем есть что сравнить
// (#409). Разделение принципиальное: удаление кадров лишает доказательств, но
// не должно снижать планку.
//
// `ids` здесь и есть набор: `docs-accept.mjs` передаёт DOC_SCREENSHOTS, а
// `verifyDocsCandidate` до этого отказывает, если набор сцен в кандидате не
// совпал с ожидаемым. Отдельный параметр размера (как в golden, где отчёт
// бывает частичным) поэтому не нужен — но пустой `ids` означает, что считать
// не от чего, и это отказ, а не ноль.
if (!ids.length) {
return { ...empty, refusal: 'набор сценариев пуст: порог свидетелей считать не от чего' };
}
const withCommitted = ids.filter((id) => committed[id]);
const floor = docsWitnessFloor(withCommitted.length);
const floor = docsWitnessFloor(ids.length);
const witnesses = withCommitted.filter((id) => !announced.has(id)
&& committed[id] === candidate[id]);
if (!skipWitnesses && witnesses.length < floor) {
return { ...empty, refusal: 'кадров-свидетелей недостаточно:'
+ ` ${witnesses.length} из необходимых ${floor}.`
// Числа возвращаются и при отказе: вызывающий печатает свой вердикт, и
// сочинять их заново ему не из чего.
return { ...empty, witnesses, floor, refusal: 'кадров-свидетелей недостаточно:'
+ ` ${witnesses.length} из необходимых ${floor}`
+ ` (сцен в наборе ${ids.length}, с закоммиченным кадром ${withCommitted.length}).`
+ ' Свидетель — необъявленный кадр, совпавший с закоммиченным байт-в-байт;'
+ ' только он доказывает, что среда съёмки та же. Если перерисовка'
+ ' действительно тотальная и осознанная — --no-witnesses --reason="…"'