ci: proof различает продуктовое дерево и overlay эталонов (#573)

Приёмка эталонов на beta.3 (`ad4000f9`) стоила второго полного Validate —
22 минуты, из них 17–22 на шард мутантов. Причина одна: корпус отпечатка
(`source-fingerprint.mjs`) называет `demo/golden` строкой-каталогом, а
замыкание входов раскрывало каталог во все текстовые файлы под ним, включая
`baselines-index.json`. Индекс становился входом smoke, performance_smoke и
каждого гарда через `serve.mjs`: на реальной паре C→B ключи smoke/perf были
DIFFERENT, отпечатки 181 из 183 браузерных свидетелей менялись, журнал их не
пропускал.

- `check-inputs.mjs`: `BASELINE_OVERLAY` — раскрытие каталога не выдаёт
  overlay; явный корень golden и явная ссылка на файл — как были. На паре
  C→B: ключи smoke/perf/parity/backend same, golden DIFFERENT; отпечатки
  743 из 744 равны; план мутантов B с журналом C — 0–1 на шард вместо 38–44
- `ci-proof.mjs`: составное evidence — product tree без overlay, overlay
  (tree, sha256 индекса, run из `Baseline-Reviewed`), content-ключи всех
  реюзных job (исполненных тоже); `evaluateCiProof({expected, reviewedRun})`
  сверяет с локальным расчётом, fail-closed на ключ, tree, индекс, reviewed
  run, маркер с чужим ключом; proof без evidence при ожиданиях — stale
- `release-gate.mjs` / `release-prerelease.mjs`: ожидания считаются на
  checkout кандидата (`candidateExpectations`), чужой checkout — notice
- мутанты: `baseline-overlay-leaks-into-every-key`,
  `proof-trusts-evidence-it-could-verify`,
  `reused-marker-key-unchecked-against-candidate`,
  `product-tree-identity-counts-baselines`; перенацелен
  `ci-proof-ignores-run-attempt`
- docs: TESTING (правило overlay), DEVELOPMENT (evidence в release proof),
  STATUS

Issue: #573
User-Visible: no
This commit is contained in:
Claude
2026-09-17 22:00:15 +03:00
parent 06bf9b6921
commit 47f36e571c
14 changed files with 578 additions and 29 deletions
+4 -2
View File
@@ -12,7 +12,7 @@ import { spawnSync } from 'node:child_process';
import { createInterface } from 'node:readline/promises';
import { stdin, stdout } from 'node:process';
import { assertReleaseContract } from './release-contract.mjs';
import { classifyValidateProofs } from './release-gate.mjs';
import { candidateExpectations, classifyValidateProofs } from './release-gate.mjs';
import { assertBundleManifest } from './bundle-tree.mjs';
import { SUMS_FILE, compareSums, formatSums, parseSums, sumsOfDirectory } from './release-assets.mjs';
import {
@@ -416,7 +416,9 @@ if (invokedDirectly) {
]);
const tree = run('git', ['rev-parse', `${sha}^{tree}`]).stdout;
const token = run('gh', ['auth', 'token']).stdout;
const verdict = await classifyValidateProofs({ runs, repo, sha, tree, token });
// #573: локальный публикатор стоит на checkout кандидата — сверяет evidence.
const expected = candidateExpectations({ sha, root });
const verdict = await classifyValidateProofs({ runs, repo, sha, tree, token, expected });
if (verdict.status !== 'green') {
throw new Error(`Exact-SHA Validate proof is ${verdict.status} for ${sha}: ${verdict.note}`);
}