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
+14 -1
View File
@@ -492,7 +492,20 @@ requires a complete Validate proof for its SHA and
Git tree (#541). The proof is tied to the workflow run ID and attempt and lists
both the requested checks and the jobs that actually executed. A skipped heavy job counts only
when its content-addressed reuse marker names an independently verified
successful source job. Review, merge and release use the
successful source job. Since #573 the proof also carries composite evidence:
the identity of the product tree (every tracked path except the accepted
golden overlay `demo/golden/baselines/**`), the overlay itself (its Git tree,
the SHA-256 of `baselines-index.json` and the run named by the commit's
`Baseline-Reviewed:` trailer) and the content key of every reusable job,
executed or reused. Release consumers standing on the candidate checkout
(`release-gate.mjs`, `release-prerelease.mjs`) recompute all of it locally and
fail closed on any mismatch, on a reused marker whose key is not the
candidate's, and on a declared review run that does not exist, was cancelled
or is not a Validate run; a proof without the block is stale for them. The
practical consequence is the beta.3 path: a candidate red only in golden,
then a baseline-only commit that reuses smoke, performance smoke, parity and
backend from the candidate's green jobs, skips every caught witness in the
mutation ledger and re-runs golden, preflight and frontend only. Review, merge and release use the
same `missing` / `pending` / `cancelled` / `stale` / `failed` state machine. A
cancelled or light run is not a release verdict and cannot hide an older full
failure; a later complete full proof can refresh it (#511). The release also requires Full