mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
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:
@@ -1,13 +1,30 @@
|
||||
// Exact-SHA GitHub Actions gate used by release workflows. Prereleases require
|
||||
// Validate; stable releases additionally require the dedicated full
|
||||
// performance workflow.
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
CI_PROOF_POLICIES, evaluateCiProof, githubCandidateTree,
|
||||
CI_PROOF_POLICIES, evaluateCiProof, githubCandidateTree, localEvidence,
|
||||
loadGithubProofContext, selectCiProofVerdict,
|
||||
} from './ci-proof.mjs';
|
||||
|
||||
/**
|
||||
* #573: ожидания потребителя. Гейты релиза стоят на checkout кандидата, и
|
||||
* тогда evidence proof (product tree, overlay эталонов, content-ключи,
|
||||
* reviewed run) не принимается на веру, а сверяется с тем, что посчитано
|
||||
* здесь. Checkout не на кандидате — считать нечего, и об этом говорится вслух.
|
||||
*/
|
||||
export function candidateExpectations({ sha, root = process.cwd(), log = console.log } = {}) {
|
||||
let head = null;
|
||||
try { head = execFileSync('git', ['-C', root, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim(); } catch { head = null; }
|
||||
if (!sha || head !== sha) {
|
||||
log(`::notice::checkout ${head ? head.slice(0, 8) : 'absent'} is not the candidate ${String(sha).slice(0, 8)} — proof evidence is verified against GitHub only (#573)`);
|
||||
return null;
|
||||
}
|
||||
return localEvidence(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* The verdict is the LATEST run that was not cancelled (#511). A cancelled run
|
||||
* proves nothing either way — concurrency or a hand superseded it — and the
|
||||
@@ -37,7 +54,7 @@ const newestFirst = (runs) => [...(Array.isArray(runs) ? runs : [])].sort((a, b)
|
||||
|
||||
/** #541: proof-aware verdict shared with review and merge. */
|
||||
export async function classifyValidateProofs({
|
||||
runs, repo, sha, tree, token, fetchImpl = fetch,
|
||||
runs, repo, sha, tree, token, fetchImpl = fetch, expected = null,
|
||||
loadContext = (run) => loadGithubProofContext({ repo, run, token, fetchImpl }),
|
||||
}) {
|
||||
const evaluations = [];
|
||||
@@ -48,7 +65,7 @@ export async function classifyValidateProofs({
|
||||
try {
|
||||
const context = await loadContext(run);
|
||||
evaluations.push(evaluateCiProof({
|
||||
run, ...context, candidate: { sha, tree }, policy: CI_PROOF_POLICIES.release,
|
||||
run, ...context, candidate: { sha, tree }, policy: CI_PROOF_POLICIES.release, expected,
|
||||
}));
|
||||
} catch (error) {
|
||||
evaluations.push({
|
||||
@@ -72,12 +89,18 @@ const sleep = (ms) => new Promise((done) => setTimeout(done, ms));
|
||||
|
||||
export async function waitForGreenWorkflow({
|
||||
repo, sha, token, workflow = 'validate.yml', label = 'Validate', timeoutMs = 60 * 60 * 1000,
|
||||
root = process.cwd(),
|
||||
}) {
|
||||
if (!repo || !sha || !token || !workflow) throw new Error('repo, sha, token and workflow are required');
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
const url = workflowRunsUrl({ repo, workflow, sha });
|
||||
const proofRequired = workflow === 'validate.yml';
|
||||
const tree = proofRequired ? await githubCandidateTree({ repo, sha, token }) : null;
|
||||
const expected = proofRequired ? candidateExpectations({ sha, root }) : null;
|
||||
if (expected) {
|
||||
console.log(`candidate evidence: product tree ${expected.product.tree.slice(0, 12)}, baselines `
|
||||
+ `${expected.baselines.tree?.slice(0, 12) || 'none'}, reviewed run ${expected.baselines.reviewedRun || 'none'} (#573)`);
|
||||
}
|
||||
while (true) {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
@@ -92,7 +115,7 @@ export async function waitForGreenWorkflow({
|
||||
const runs = Array.isArray(body?.workflow_runs) ? body.workflow_runs : [];
|
||||
const latest = latestRelevantRun(runs);
|
||||
const verdict = proofRequired
|
||||
? await classifyValidateProofs({ runs, repo, sha, tree, token })
|
||||
? await classifyValidateProofs({ runs, repo, sha, tree, token, expected })
|
||||
: { status: classifyValidateRuns(runs) === 'success' ? 'green'
|
||||
: classifyValidateRuns(runs) === 'fail' ? 'failed' : 'pending', url: latest?.html_url, note: '' };
|
||||
if (verdict.status === 'failed') {
|
||||
|
||||
Reference in New Issue
Block a user