mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 04:38:55 +00:00
ci: reviewed run судится только release-потребителем (#573, ревью r1 M1)
`loadGithubProofContext` спрашивал объявленный `Baseline-Reviewed` run для любого потребителя, а `evaluateCiProof` судил его при `reviewedRun: null` — merge и review начинали зависеть от доступности старого run по чужой причине. Теперь запрос делается только с `withReviewedRun` (release-gate передаёт его вместе с ожиданиями), а проверка стоит внутри `if (expected)` — рядом со сверкой evidence, где ей и место. Тест: без ожиданий merge/review green при reviewedRun undefined/null/пустом; фейковый fetch доказывает, что запроса нет. Issue: #573 User-Visible: no
This commit is contained in:
+19
-14
@@ -287,12 +287,23 @@ export function evaluateCiProof({
|
||||
return result('stale', 'run event differs from proof event');
|
||||
if (policy?.full && !asBool(proof.request?.full)) return result('stale', 'proof is light; full gates were not requested');
|
||||
if (policy?.mutants && !asBool(proof.request?.mutants)) return result('stale', 'proof has no requested mutant jobs');
|
||||
// #573: потребитель, у которого есть checkout кандидата, сверяет составное
|
||||
// evidence, а не верит ему. Proof без блока при наличии ожиданий устарел.
|
||||
// #573: потребитель, у которого есть checkout кандидата (release), сверяет
|
||||
// составное evidence, а не верит ему. Proof без блока при наличии ожиданий
|
||||
// устарел. Объявленный run просмотра кадров проверяется ТОЛЬКО здесь же:
|
||||
// review и merge ожиданий не передают и лишнего запроса к API не делают
|
||||
// (ревью r1, M1) — их семантика #541 не меняется.
|
||||
if (expected) {
|
||||
if (!proof.evidence) return result('stale', 'proof predates composite evidence (#573)');
|
||||
const mismatch = evidenceMismatch(proof.evidence, expected);
|
||||
if (mismatch) return result('failed', `evidence does not match the candidate checkout — ${mismatch}`);
|
||||
const declared = proof.evidence.baselines?.reviewedRun ?? null;
|
||||
if (declared) {
|
||||
const source = reviewedRun?.run;
|
||||
if (!source || runIdOf(source) !== declared || !/validate\.yml$/.test(String(source.path || source.workflow || 'validate.yml'))
|
||||
|| source.status !== 'completed' || source.conclusion === 'cancelled') {
|
||||
return result('failed', `Baseline-Reviewed run ${declared} is missing, cancelled or not a Validate run`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (proof.evidence) {
|
||||
for (const id of REUSE_JOBS) {
|
||||
@@ -300,14 +311,6 @@ export function evaluateCiProof({
|
||||
if (claim?.mode === 'reused' && claim.reuse?.key !== proof.evidence.keys?.[id])
|
||||
return result('failed', `${id}: reused marker key differs from the candidate content key`);
|
||||
}
|
||||
const declared = proof.evidence.baselines?.reviewedRun ?? null;
|
||||
if (declared && reviewedRun !== undefined) {
|
||||
const source = reviewedRun?.run;
|
||||
if (!source || runIdOf(source) !== declared || !/validate\.yml$/.test(String(source.path || source.workflow || 'validate.yml'))
|
||||
|| source.status !== 'completed' || source.conclusion === 'cancelled') {
|
||||
return result('failed', `Baseline-Reviewed run ${declared} is missing, cancelled or not a Validate run`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const derived = requiredCheckIds(proof);
|
||||
if (!sameSet(derived, proof.requiredChecks || []))
|
||||
@@ -404,7 +407,7 @@ export async function githubCandidateTree({ repo, sha, token, fetchImpl = fetch
|
||||
return row?.tree?.sha || null;
|
||||
}
|
||||
|
||||
export async function loadGithubProofContext({ repo, run, token, fetchImpl = fetch }) {
|
||||
export async function loadGithubProofContext({ repo, run, token, fetchImpl = fetch, withReviewedRun = false }) {
|
||||
const runId = runIdOf(run);
|
||||
const attempt = runAttemptOf(run);
|
||||
const name = ciProofArtifactName(runId, attempt);
|
||||
@@ -438,17 +441,19 @@ export async function loadGithubProofContext({ repo, run, token, fetchImpl = fet
|
||||
);
|
||||
reuseRuns.set(sourceKey, { run: sourceRun, jobs: sourceJobs?.jobs || [] });
|
||||
}
|
||||
// #573: объявленный человеком run просмотра кадров обязан существовать.
|
||||
// #573: объявленный человеком run просмотра кадров обязан существовать —
|
||||
// спрашивает только release-потребитель (`withReviewedRun`); review и merge
|
||||
// этот запрос не делают и от доступности старого run не зависят.
|
||||
let reviewedRun;
|
||||
const declared = proof?.evidence?.baselines?.reviewedRun;
|
||||
if (declared) {
|
||||
if (withReviewedRun && declared) {
|
||||
try {
|
||||
reviewedRun = { run: await githubJson(`https://api.github.com/repos/${repo}/actions/runs/${declared}`, token, fetchImpl) };
|
||||
} catch {
|
||||
reviewedRun = null;
|
||||
}
|
||||
}
|
||||
return { proof, jobs, reuseRuns, reviewedRun };
|
||||
return { proof, jobs, reuseRuns, ...(reviewedRun !== undefined ? { reviewedRun } : {}) };
|
||||
}
|
||||
|
||||
if (isMainModule(import.meta.url)) {
|
||||
|
||||
@@ -55,7 +55,8 @@ 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, expected = null,
|
||||
loadContext = (run) => loadGithubProofContext({ repo, run, token, fetchImpl }),
|
||||
// #573: reviewed run спрашивается у GitHub только вместе с ожиданиями (release)
|
||||
loadContext = (run) => loadGithubProofContext({ repo, run, token, fetchImpl, withReviewedRun: Boolean(expected) }),
|
||||
}) {
|
||||
const evaluations = [];
|
||||
for (const run of newestFirst(runs)) {
|
||||
|
||||
Reference in New Issue
Block a user