Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions tools/lib/release-evidence-consistency.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,39 @@ Deno.test('release evidence closure requires durable final commit and workflow r
'release note does not reference the GitHub release',
]);
});

Deno.test('release evidence closure accepts the two-phase patch-release tag flow', () => {
// 0.41.2: the local patch-release tagged the version (tag snapshot carries
// the patch-release record), the CI publish-existing closed the release
// with its own record. Different ids are valid across the two kinds.
const failures = validateReleaseEvidenceClosure({
version: '0.41.2',
record: {
tagCommit: 'tag-sha',
finalEvidenceCommit: 'final-sha',
successfulReleaseRun: 'https://github.com/example/actions/runs/2',
releaseUrl: 'https://github.com/example/releases/tag/v0.41.2',
},
tagIsAncestorOfFinal: true,
finalIsAncestorOfHead: true,
tagEvidence: {
id: 'patch-run-id',
kind: 'patch-release',
targetVersion: '0.41.2',
status: 'running',
steps: [{ name: 'tag release', status: 'passed' }],
},
finalEvidence: {
id: 'publish-run-id',
kind: 'publish-existing',
targetVersion: '0.41.2',
status: 'completed',
completedAt: '2026-07-27T01:45:00.000Z',
steps: [{ name: 'create GitHub release', status: 'passed' }],
},
releaseNote:
'Final evidence: final-sha\nRun: https://github.com/example/actions/runs/2\nRelease: https://github.com/example/releases/tag/v0.41.2',
});

assertEquals(failures, []);
});
10 changes: 9 additions & 1 deletion tools/lib/release-evidence-consistency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ReleaseClosureRecord {

export interface ReleaseEvidenceSnapshot {
id: string;
kind?: string;
targetVersion: string;
status: string;
completedAt?: string;
Expand All @@ -25,7 +26,14 @@ export interface ReleaseEvidenceClosureInput {

export function validateReleaseEvidenceClosure(input: ReleaseEvidenceClosureInput): string[] {
const failures: string[] = [];
if (input.tagEvidence.id !== input.finalEvidence.id) {
// Same-run flow: the tag snapshot and the final record share one run id.
// Two-phase flow: a local patch-release tagged the version and the CI
// publish-existing closed it, so the tag snapshot carries the
// patch-release record while the final record is publish-existing (0.41.2).
const sameRun = input.tagEvidence.id === input.finalEvidence.id;
const twoPhase = input.tagEvidence.kind === 'patch-release' &&
input.finalEvidence.kind === 'publish-existing';
if (!sameRun && !twoPhase) {
failures.push('tag and final evidence ids differ');
}
if (
Expand Down
Loading