mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 06:08:59 +00:00
fix: индекс эталонов хранит платформу съёмки, а не платформу приёмки (#571)
Аудит 14.09, §4 и §9. `golden-report.json` не нёс платформу вовсе, поэтому приёмщик вызывал `captureEnvironment()` у себя и записывал СВОЮ платформу как платформу кадров. На `ad4000f9` это дало `"platform": "win32"` у кадров, снятых Linux-прогоном 34853080375, а причина осознанного обхода осталась в stdout и в индекс не попала — хотя `AGENTS.md` обещает след в обоих местах. Сами PNG были и остаются целы: 101 свидетель совпал байт-в-байт. Врал провенанс. Что сделано: - отчёт съёмки получил раздел `capture`: платформа, архитектура, сборка Chromium, отпечаток материала и — в CI — прогон с попыткой и SHA. Схема отчёта поднята до 2; - приёмка читает среду съёмки из отчёта. Гейт чужой среды теперь судит обе стороны: съёмку (из отчёта) и приёмку (свою). Отказ — до единой записи; - индекс эталонов поднят до схемы 2 и различает `capturedOn` и `acceptedOn`, несёт раздел `capture` и причину осознанного обхода в `foreignCapture`; - отчёт схемы 1 платформы съёмки не несёт физически: это отдельная явная ветка, `capturedOn` уезжает `null`. Выдумывать платформу нельзя — ровно этим задача и вызвана; - `--baselines=<dir>` у приёмки: без него проверить «отказ произошёл ДО записи» можно было бы только порчей рабочего дерева, то есть никак (#556). Осознанно отменено решение #455 «не трогать run.mjs»: ради ГЕЙТА цена фингерпринта не окупалась, ради ПРОВЕНАНСА окупилась — платформу кадров знает только тот, кто их снял. Плата разовая: пересобран бандл, индекс скриншотов документации переснят отдельным коммитом. Тест #455 переписан под новый инвариант, а не удалён. Свидетели: `test/golden-capture-provenance.test.mjs` — семь проверок, все пять сценариев приёмки из issue, включая «отказ до записи» и «подмена PNG и неполный артефакт по-прежнему fail-closed». Мутанты `golden-index-invents-capture-platform` (возвращает платформу приёмщика) и `golden-report-provenance-optional` (разрешает отчёт схемы 2 без провенанса) прогнаны лично: оба краснеют. npm test 2762/2761/0 fail, typecheck чистый. Issue: #571 User-Visible: no
This commit is contained in:
@@ -48,6 +48,71 @@ export const captureEnvironment = (source = process) => ({
|
||||
arch: String(source.arch || ''),
|
||||
});
|
||||
|
||||
/** Версия схемы отчёта съёмки, в которой провенанс обязателен (#571). */
|
||||
export const CAPTURE_PROVENANCE_SCHEMA = 2;
|
||||
|
||||
/**
|
||||
* Провенанс СЪЁМКИ для отчёта (#571).
|
||||
*
|
||||
* До этой задачи отчёт платформу не нёс вовсе, и приёмщик записывал в индекс
|
||||
* эталонов СВОЮ платформу как платформу кадров. На `ad4000f9` это дало
|
||||
* `"platform": "win32"` у кадров, снятых Linux-прогоном 34853080375: индекс
|
||||
* утверждал неправду, а причина осознанного обхода осталась только в stdout.
|
||||
*
|
||||
* Поэтому провенанс собирается там, где кадры снимаются, и уезжает в отчёт:
|
||||
* платформа, архитектура, сборка Chromium, отпечаток материала и — если съёмка
|
||||
* шла в CI — прогон с попыткой. Последнее не косметика: артефакт можно скачать
|
||||
* и принять спустя сутки, и ссылка на прогон единственная, что связывает
|
||||
* картинки с их происхождением.
|
||||
*/
|
||||
export const captureProvenance = ({
|
||||
chromium = null, buildFingerprint = null, source = process, env = process.env,
|
||||
} = {}) => {
|
||||
const { platform, arch } = captureEnvironment(source);
|
||||
const run = String(env?.GITHUB_RUN_ID ?? '').trim();
|
||||
const attempt = String(env?.GITHUB_RUN_ATTEMPT ?? '').trim();
|
||||
const repository = String(env?.GITHUB_REPOSITORY ?? '').trim();
|
||||
const sha = String(env?.GITHUB_SHA ?? '').trim();
|
||||
return {
|
||||
platform,
|
||||
arch,
|
||||
chromium: chromium || null,
|
||||
buildFingerprint: buildFingerprint || null,
|
||||
// Пустой объект вместо `null` был бы ложью «CI известен, полей нет».
|
||||
ci: run ? {
|
||||
repository: repository || null,
|
||||
run: Number(run) || null,
|
||||
attempt: Number(attempt) || 1,
|
||||
sha: sha || null,
|
||||
} : null,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Провенанс отчёта в пригодном для решения виде: `{ provenance, legacy }`.
|
||||
*
|
||||
* `legacy: true` — отчёт старой схемы, платформы съёмки в нём нет физически.
|
||||
* Такой отчёт не отвергается (артефакты живут дольше схемы), но и не выдаёт
|
||||
* себя за проверенный: платформа съёмки остаётся `null`, и вызывающий обязан
|
||||
* решить это явной веткой, а не молча подставить свою.
|
||||
*/
|
||||
export const reportCaptureProvenance = (report = {}) => {
|
||||
const schema = Number(report?.schema) || 1;
|
||||
const provenance = report?.capture;
|
||||
if (schema >= CAPTURE_PROVENANCE_SCHEMA) {
|
||||
// Fail-closed: схема обещает провенанс, значит его отсутствие — поломка
|
||||
// инструмента съёмки, а не повод угадывать.
|
||||
if (!provenance || typeof provenance !== 'object') {
|
||||
throw new Error(`отчёт схемы ${schema} обязан нести раздел capture с провенансом съёмки (#571)`);
|
||||
}
|
||||
if (!provenance.platform) {
|
||||
throw new Error(`отчёт схемы ${schema} не называет платформу съёмки (#571)`);
|
||||
}
|
||||
return { provenance, legacy: false };
|
||||
}
|
||||
return { provenance: null, legacy: true };
|
||||
};
|
||||
|
||||
/**
|
||||
* Разрешён ли осознанный обход. Возвращает причину или `null`.
|
||||
* Пустая строка — не причина: обход без записанной причины неотличим от
|
||||
|
||||
@@ -9965,6 +9965,29 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: " if (settings?.fill_mode === 'never-a-fill-mode') return spaceFill;",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'golden-index-invents-capture-platform',
|
||||
guard: 'node --test --test-name-pattern="#571" test/golden-capture-provenance.test.mjs',
|
||||
because: '#571: платформу кадров знает только тот, кто их снял. Пока приёмщик писал в индекс '
|
||||
+ 'свою, артефакт Linux-прогона 34853080375, принятый на Windows, оставил в `ad4000f9` '
|
||||
+ 'запись «сняты на win32» — индекс утверждал неправду, и заметить это было нечем',
|
||||
patches: [{
|
||||
file: 'demo/golden/accept.mjs',
|
||||
find: ' capturedOn: capturedOn,',
|
||||
replace: ' capturedOn: acceptance.platform, // mutant: снова платформа приёмщика',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'golden-report-provenance-optional',
|
||||
guard: 'node --test --test-name-pattern="#571 схема 2" test/golden-capture-provenance.test.mjs',
|
||||
because: '#571: схема 2 обещает провенанс съёмки. Если его отсутствие перестаёт быть отказом, '
|
||||
+ 'сломанный инструмент съёмки тихо вернёт индекс к догадкам вместо факта',
|
||||
patches: [{
|
||||
file: 'scripts/capture-environment.mjs',
|
||||
find: " throw new Error(`отчёт схемы ${schema} обязан нести раздел capture с провенансом съёмки (#571)`);",
|
||||
replace: " return { provenance: null, legacy: true }; // mutant: молча считаем отчёт старым",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'furniture-symbol-may-keep-inner-padding',
|
||||
guard: 'node --test --test-name-pattern="AC1" test/furniture-visual-bounds.test.mjs',
|
||||
|
||||
Reference in New Issue
Block a user