Accept cumulative prerelease changelog bases (#2919)

The beta release train writes cumulative changelogs from the previous
stable, but the install packet check required the previous numbered
beta. Accept both supported comparison modes and continue rejecting
unrelated, missing or ambiguous bases.

Validation: targeted race tests and the failed beta.3 snapshot
regression, plus governance audits and native commit and push checks.
This commit is contained in:
rcourtman
2026-10-14 00:09:24 +01:00
committed by GitHub
2 changed files with 50 additions and 10 deletions
@@ -1301,11 +1301,6 @@ func TestCurrentPrereleasePacketTracksInstallMetadata(t *testing.T) {
if _, _, ok := strings.Cut(version, "-"); !ok {
t.Fatalf("current prerelease %q has no stable target", version)
}
comparisonVersion, ok := previousPrereleaseVersion(version)
if !ok {
comparisonVersion = previous
}
releaseNotesPath := repoFile("docs", "releases", "RELEASE_NOTES_v"+version+".md")
changelogPath := repoFile("docs", "releases", "V6_CHANGELOG_v"+version+".md")
@@ -1319,16 +1314,23 @@ func TestCurrentPrereleasePacketTracksInstallMetadata(t *testing.T) {
"The rollback target is stable `v"+previous+"`",
)
assertFileDoesNotContain(t, releaseNotesPath, "## Fixes")
comparisonSummary := "This changelog describes the changes since `v" + comparisonVersion + "`"
if version == "6.4.0-rc.10" {
comparisonSummary = "The `v6.4.0-rc.9` release staged an immutable draft, tag, and exact-version artifacts but did not activate publicly."
assertFileContainsAllNormalized(t, changelogPath,
"The `v6.4.0-rc.9` release staged an immutable draft, tag, and exact-version artifacts but did not activate publicly.")
} else {
content, err := os.ReadFile(changelogPath)
if err != nil {
t.Fatalf("read %s: %v", changelogPath, err)
}
if !prereleaseChangelogHasSupportedBase(string(content), version, previous) {
t.Fatalf("%s must describe changes since the previous numbered prerelease or stable v%s", changelogPath, previous)
}
}
assertFileContainsAllNormalized(t, changelogPath,
"Version: `v"+version+"`",
"Previous stable: `v"+previous+"`",
"Rollback target: `v"+previous+"`",
"Promotion path: ",
comparisonSummary,
"Windows signing decision: prereleases publish checksum- and detached-signature-verified Windows agents without Authenticode",
"Mobile decision: ",
"no companion mobile build or store rollout is required",
@@ -4952,6 +4954,44 @@ func TestCommittedReleaseVisualSidecarsCarrySelectionEvidence(t *testing.T) {
}
}
// Metadata defaults to the previous numbered prerelease. The release train can
// explicitly select the previous stable for a cumulative changelog instead.
func prereleaseChangelogHasSupportedBase(content, version, previousStable string) bool {
matches := regexp.MustCompile("This changelog describes the changes since `v([^`]+)`").FindAllStringSubmatch(normalizedInstallTestWhitespace(content), -1)
if len(matches) != 1 {
return false
}
base := matches[0][1]
if base == previousStable {
return true
}
previous, ok := previousPrereleaseVersion(version)
return ok && base == previous
}
func TestPrereleaseChangelogSupportsIncrementalAndCumulativeBases(t *testing.T) {
for _, tc := range []struct {
name, version, content string
want bool
}{
{"incremental beta", "6.6.0-beta.3", "This changelog describes the changes since `v6.6.0-beta.2`", true},
{"cumulative beta", "6.6.0-beta.3", "This changelog describes the changes since\n`v6.5.0`", true},
{"incremental candidate", "6.6.0-rc.2", "This changelog describes the changes since `v6.6.0-rc.1`", true},
{"first beta", "6.6.0-beta.1", "This changelog describes the changes since `v6.5.0`", true},
{"unrelated stable", "6.6.0-beta.3", "This changelog describes the changes since `v6.4.0`", false},
{"older beta", "6.6.0-beta.3", "This changelog describes the changes since `v6.6.0-beta.1`", false},
{"other stage", "6.6.0-rc.2", "This changelog describes the changes since `v6.6.0-beta.1`", false},
{"missing comparison", "6.6.0-beta.3", "## Changes", false},
{"conflicting comparisons", "6.6.0-beta.3", "This changelog describes the changes since `v6.5.0`\nThis changelog describes the changes since `v6.6.0-beta.2`", false},
} {
t.Run(tc.name, func(t *testing.T) {
if got := prereleaseChangelogHasSupportedBase(tc.content, tc.version, "6.5.0"); got != tc.want {
t.Fatalf("supported base = %v, want %v", got, tc.want)
}
})
}
}
func assertFileContainsAllNormalized(t *testing.T, path string, required ...string) {
t.Helper()
content, err := os.ReadFile(path)
@@ -301,8 +301,8 @@ func previousPrereleaseVersion(version string) (string, bool) {
return "", false
}
// The release train cuts several betas before its candidate. The second beta's changelog
// is written as the changes since the first, so the checks that read it must expect that.
// This predecessor is the metadata tool's default comparison base. A cumulative
// changelog can explicitly compare with the previous stable instead.
func TestPreviousPrereleaseVersionFollowsItsOwnStage(t *testing.T) {
for _, tc := range []struct {
version, want string