mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 22:49:16 +00:00
test: golden acceptance keeps environment witnesses (#355)
Declaring the whole matrix in --expect-change could accept a completely foreign capture (different font stack, different machine): no undeclared passed scenes would remain, and undeclared passed scenes are exactly what proves the capture environment equals the accepted baseline's. The realistic failure is fatigue, not malice — a mass framing change where the author lists "everything that went red", accidentally sweeping in scenes that diverged because of the environment. Acceptance now requires a witness floor: after subtracting --expect-change/--expect-new, at least min(10, 10% of baseline scenes) undeclared scenes must match their accepted baselines BYTE-FOR-BYTE (a sub-threshold 'passed' proves nothing about the environment — #351). A truly total repaint passes only with an explicit --no-witnesses --reason="…", and the reason is written into the baseline manifest — a trace in the artifact and its git history, not just in the shell history. A first-ever capture with no baselines requires no witnesses: every frame there is declared in --expect-new anyway. Issue: #355 User-Visible: no
This commit is contained in:
@@ -4,6 +4,13 @@
|
||||
*
|
||||
* node scripts/golden-accept.mjs --reviewed --expect-change=<id,id>
|
||||
* node scripts/golden-accept.mjs --reviewed --expect-new=<id,id>
|
||||
* node scripts/golden-accept.mjs --reviewed --expect-change=… --no-witnesses --reason="…"
|
||||
*
|
||||
* Floor свидетелей (#355): после вычета объявленных сцен должно остаться
|
||||
* достаточно необъявленных, совпавших с эталоном байт-в-байт, — они доказывают,
|
||||
* что среда съёмки та же, что у принятого эталона. Тотальная перерисовка
|
||||
* обходится только явным `--no-witnesses --reason="…"`; причина уезжает в
|
||||
* манифест эталонов.
|
||||
*
|
||||
* Обёртка появилась потому, что до #344 любой `.mjs` из `demo/golden` входил в
|
||||
* корпус отпечатка, и правка инструмента приёмки объявляла устаревшими бандл и
|
||||
|
||||
@@ -141,3 +141,65 @@ export const goldenAcceptancePlan = ({
|
||||
}
|
||||
return { replace, keep, hashes };
|
||||
};
|
||||
|
||||
/**
|
||||
* Floor сцен-свидетелей (#355).
|
||||
*
|
||||
* Смысл непереименованных прошедших сцен — быть СВИДЕТЕЛЯМИ среды: если бы
|
||||
* растеризация или окружение съёмки отличались от эталонных, они бы разошлись.
|
||||
* Перечислив ВСЮ матрицу в `--expect-change`, можно принять полностью чужую
|
||||
* съёмку — свидетелей не останется, а след останется только в истории команды.
|
||||
* Практический сценарий — не злой умысел, а усталость: массовая framing-правка,
|
||||
* автор перечисляет «всё, что покраснело», захватывая и сцены, разошедшиеся по
|
||||
* причине среды.
|
||||
*
|
||||
* Свидетель — необъявленная сцена, чей кандидат совпал с принятым эталоном
|
||||
* БАЙТ-В-БАЙТ: `passed` означает лишь «в пределах порога» (#351), а среду
|
||||
* доказывает только точное совпадение. Floor — 10 свидетелей или 10% сцен с
|
||||
* эталонами, что меньше; матрица без эталонов (первичная съёмка) свидетелей
|
||||
* требовать не может — там каждый кадр и так объявляется в `--expect-new`.
|
||||
*
|
||||
* Осознанный обход для действительно тотальных перерисовок — `--no-witnesses`
|
||||
* с обязательной причиной: она уезжает в манифест эталонов, то есть в артефакт
|
||||
* и его git-историю, а не только в историю shell.
|
||||
*/
|
||||
export const goldenWitnessFloor = (baselineCount) => (baselineCount > 0
|
||||
? Math.min(10, Math.ceil(baselineCount * 0.1))
|
||||
: 0);
|
||||
|
||||
export const goldenWitnessRefusal = ({
|
||||
results, declared = [], declaredNew = [], previousHashes = {},
|
||||
skipWitnesses = false, skipReason = '',
|
||||
}) => {
|
||||
if (skipWitnesses) {
|
||||
if (typeof skipReason !== 'string' || !skipReason.trim()) {
|
||||
return { refusal: '--no-witnesses требует --reason="…": причина обхода'
|
||||
+ ' обязана остаться в отчёте приёмки', witnesses: [], floor: 0 };
|
||||
}
|
||||
return { refusal: null, witnesses: [], floor: 0 };
|
||||
}
|
||||
const accepted = new Set([...declared, ...declaredNew].filter(Boolean));
|
||||
const withBaseline = results.filter((result) => result.status !== 'missing-baseline');
|
||||
const floor = goldenWitnessFloor(withBaseline.length);
|
||||
const witnesses = withBaseline
|
||||
.filter((result) => !accepted.has(result.id)
|
||||
&& result.status === 'passed'
|
||||
&& typeof result.actualSha256 === 'string'
|
||||
&& result.actualSha256 === previousHashes[result.id])
|
||||
.map((result) => result.id)
|
||||
.sort();
|
||||
if (witnesses.length < floor) {
|
||||
return {
|
||||
refusal: 'сцен-свидетелей среды недостаточно:'
|
||||
+ ` ${witnesses.length} из необходимых ${floor}`
|
||||
+ ` (эталонных сцен ${withBaseline.length}, объявлено ${accepted.size}).`
|
||||
+ ' Свидетель — необъявленная сцена, совпавшая с эталоном байт-в-байт;'
|
||||
+ ' именно они доказывают, что среда съёмки та же, что у принятого'
|
||||
+ ' эталона. Если перерисовка действительно тотальная и осознанная —'
|
||||
+ ' --no-witnesses --reason="…" оставит причину в манифесте эталонов',
|
||||
witnesses,
|
||||
floor,
|
||||
};
|
||||
}
|
||||
return { refusal: null, witnesses, floor };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user