Prepare v6.0.5 RC1 release packet
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Canonical Governance / governance (push) Waiting to run
Helm CI / Lint and Render Chart (push) Waiting to run
Core E2E Tests / Playwright Core E2E (push) Waiting to run

Refs #1530

Refs #1531

Refs #1471
This commit is contained in:
rcourtman 2026-07-07 00:12:09 +01:00
parent 4b669ad9b5
commit dded079297
13 changed files with 180 additions and 67 deletions

View file

@ -6,7 +6,7 @@ set -euo pipefail
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
DOCKER_IMAGE_REPO="${DOCKER_IMAGE_REPO:-rcourtman/pulse}"
CANONICAL_DEFAULT_PULSE_VERSION="6.0.5"
CANONICAL_DEFAULT_PULSE_VERSION="6.0.5-rc.1"
resolve_default_pulse_version() {
if [ -n "${PULSE_IMAGE_VERSION:-}" ]; then

View file

@ -308,6 +308,71 @@ func TestCurrentStablePatchReleasePacketTracksInstallMetadata(t *testing.T) {
)
}
func TestCurrentSupportPrereleasePacketTracksInstallMetadata(t *testing.T) {
version := currentReleaseVersion(t)
if !isPrereleaseVersion(version) {
t.Skip("current release is stable")
}
previous, ok := previousStableForPrereleaseVersion(version)
if !ok {
t.Skip("current prerelease does not have a previous stable patch")
}
releaseNotesPath := repoFile("docs", "releases", "RELEASE_NOTES_v"+version+".md")
changelogPath := repoFile("docs", "releases", "V6_CHANGELOG_v"+version+".md")
assertFileContainsAllNormalized(t, releaseNotesPath,
"`v"+version+"` is a release candidate",
"stable `v"+previous+"`",
"Patrol Gemini model readiness",
"remembered-login submit persistence",
"Proxmox SMART temperature fallback",
"direct SATA/SAT disks",
"rollback target for this release candidate is `v"+previous+"`",
)
assertFileContainsAll(t, changelogPath,
"Version: `v"+version+"`",
"Rollback target: `v"+previous+"`",
"Promotion path: release candidate from `main`",
"Gemini tool-call capability",
"Remembered-login state now persists",
"Proxmox SMART temperature collection",
"explicit SAT probe",
)
assertFileContainsAll(t, repoFile("docs", "RELEASE_NOTES.md"),
"docs/releases/RELEASE_NOTES_v"+version+".md",
"docs/releases/V6_CHANGELOG_v"+version+".md",
"current v6 support release candidate packet",
)
assertFileContainsAll(t, repoFile("docs", "UPGRADE_v6.md"),
"docs/releases/RELEASE_NOTES_v"+version+".md",
"docs/releases/V6_CHANGELOG_v"+version+".md",
"current v6 support release candidate packet",
)
assertFileContainsAll(t, repoFile("deploy", "helm", "pulse", "Chart.yaml"),
"version: "+version,
`appVersion: "`+version+`"`,
"raw.githubusercontent.com/rcourtman/Pulse/v"+version+"/docs/images/pulse-logo.svg",
"blob/v"+version+"/docs/KUBERNETES.md",
)
assertFileContainsAll(t, repoFile("deploy", "helm", "pulse", "README.md"),
"Version-"+shieldsBadgeMessage(version)+"-informational",
"AppVersion-"+shieldsBadgeMessage(version)+"-informational",
"Autogenerated from chart metadata using [helm-docs v1.14.2]",
)
assertFileContainsAll(t, repoFile("docker-compose.yml"),
"image: ${PULSE_IMAGE:-rcourtman/pulse:"+version+"}",
)
assertFileContainsAll(t, repoFile("scripts", "install-docker.sh"),
`CANONICAL_DEFAULT_PULSE_VERSION="`+version+`"`,
)
assertFileContainsAllNormalized(t, repoFile("docs", "release-control", "v6", "internal", "subsystems", "deployment-installability.md"),
"The active support prerelease `v"+version+"` cut sets the repo-root `VERSION`, repo-root `docker-compose.yml` image default, `scripts/install-docker.sh` fallback, and Helm chart release metadata to the same `"+version+"` release version.",
"This support prerelease keeps `rollback_version=v"+previous+"`, publishes a versioned public GitHub prerelease plus versioned Docker and Helm artifacts, and does not move stable/latest install pointers or stable semver aliases.",
"For the active support prerelease `v"+version+"` cut, the repo-root compose default and `scripts/install-docker.sh` fallback must both pin `"+version+"` until the next governed stable cut moves them forward.",
)
}
func TestBackfillReleaseWorkflowRepairsPublishedAssetsWithoutRebuilds(t *testing.T) {
scriptBytes, err := os.ReadFile(repoFile("scripts", "backfill-release-assets.sh"))
if err != nil {

View file

@ -228,7 +228,7 @@ func TestInstallDockerProofTracksSupportPrereleaseContract(t *testing.T) {
assertFileContainsAllNormalized(t, repoFile("docs", "release-control", "v6", "internal", "subsystems", "deployment-installability.md"),
"The active support prerelease `v"+version+"` cut sets the repo-root `VERSION`, repo-root `docker-compose.yml` image default, `scripts/install-docker.sh` fallback, and Helm chart release metadata to the same `"+version+"` release version.",
"This support prerelease uses the private Pro support image path with a stable rollback reference `v"+previous+"`, no `latest` movement, no stable semver tag movement, no R2 manifest promotion, no broker download metadata promotion, and no public `rcourtman/pulse` image publication.",
"This support prerelease keeps `rollback_version=v"+previous+"`, publishes a versioned public GitHub prerelease plus versioned Docker and Helm artifacts, and does not move stable/latest install pointers or stable semver aliases.",
"For the active support prerelease `v"+version+"` cut, the repo-root compose default and `scripts/install-docker.sh` fallback must both pin `"+version+"` until the next governed stable cut moves them forward.",
)
}

View file

@ -78,6 +78,18 @@ def rc_packet_paths_for_version(version: str) -> tuple[str, str, str] | None:
)
def support_prerelease_packet_paths_for_version(version: str) -> tuple[str, str] | None:
"""Return release-notes and changelog paths for post-GA support RC versions."""
if not re.match(r"^6\.\d+\.\d+-rc\.\d+$", version):
return None
if version.startswith("6.0.0-rc."):
return None
return (
f"docs/releases/RELEASE_NOTES_v{version}.md",
f"docs/releases/V6_CHANGELOG_v{version}.md",
)
def stable_packet_paths_for_version(version: str) -> tuple[str, str] | None:
"""Return the stable release-notes and changelog packet paths for a v6 stable VERSION."""
if not re.match(r"^6\.\d+\.\d+$", version):
@ -350,25 +362,40 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn(f"Pulse v{current_version}", changelog)
else:
packet_paths = rc_packet_paths_for_version(current_version)
self.assertIsNotNone(
packet_paths,
f"VERSION={current_version} does not match the 6.0.0-rc.N pattern",
support_packet_paths = support_prerelease_packet_paths_for_version(current_version)
self.assertTrue(
packet_paths is not None or support_packet_paths is not None,
f"VERSION={current_version} does not match a governed v6 prerelease packet pattern",
)
release_notes_path, changelog_path, operator_pack_path = packet_paths
if packet_paths is not None:
release_notes_path, changelog_path, operator_pack_path = packet_paths
release_notes = read(release_notes_path)
changelog = read(changelog_path)
operator_pack = read(operator_pack_path)
release_notes = read(release_notes_path)
changelog = read(changelog_path)
operator_pack = read(operator_pack_path)
self.assertIn(f"current in-repo v6 `rc.{current_version.rsplit('.', 1)[1]}` draft packet", release_index)
self.assertIn(release_notes_path, release_index)
self.assertIn(changelog_path, release_index)
self.assertIn(operator_pack_path, release_index)
self.assertNotIn("current stable v6 release packet", release_index)
self.assertIn(f"Pulse v{current_version} Draft Release Notes", release_notes)
self.assertIn(f"`v{current_version}`", release_notes)
self.assertIn(f"Pulse v{current_version} Draft Changelog", changelog)
self.assertIn(f"`v{current_version}`", operator_pack)
self.assertIn(f"current in-repo v6 `rc.{current_version.rsplit('.', 1)[1]}` draft packet", release_index)
self.assertIn(release_notes_path, release_index)
self.assertIn(changelog_path, release_index)
self.assertIn(operator_pack_path, release_index)
self.assertNotIn("current stable v6 release packet", release_index)
self.assertIn(f"Pulse v{current_version} Draft Release Notes", release_notes)
self.assertIn(f"`v{current_version}`", release_notes)
self.assertIn(f"Pulse v{current_version} Draft Changelog", changelog)
self.assertIn(f"`v{current_version}`", operator_pack)
else:
assert support_packet_paths is not None
release_notes_path, changelog_path = support_packet_paths
release_notes = read(release_notes_path)
changelog = read(changelog_path)
self.assertIn("current v6 support release candidate packet", release_index)
self.assertIn(release_notes_path, release_index)
self.assertIn(changelog_path, release_index)
self.assertIn(f"Pulse v{current_version} Release Notes", release_notes)
self.assertIn(f"`v{current_version}`", release_notes)
self.assertIn(f"Pulse v{current_version}", changelog)
def test_upgrade_guide_points_at_current_rc_support_pack(self) -> None:
upgrade_guide = read("docs/UPGRADE_v6.md")
@ -400,17 +427,27 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertNotIn("docs/releases/V6_RC_OPERATOR_SUPPORT_PACK.md", upgrade_guide)
else:
packet_paths = rc_packet_paths_for_version(current_version)
self.assertIsNotNone(
packet_paths,
f"VERSION={current_version} does not match the 6.0.0-rc.N pattern",
support_packet_paths = support_prerelease_packet_paths_for_version(current_version)
self.assertTrue(
packet_paths is not None or support_packet_paths is not None,
f"VERSION={current_version} does not match a governed v6 prerelease packet pattern",
)
current_support_pack = packet_paths[2]
self.assertIn(current_support_pack, upgrade_guide)
for _, _, _, support_pack in discover_rc_draft_packets():
if support_pack == current_support_pack:
continue
self.assertNotIn(support_pack, upgrade_guide)
self.assertNotIn("docs/releases/V6_RC_OPERATOR_SUPPORT_PACK.md", upgrade_guide)
if packet_paths is not None:
current_support_pack = packet_paths[2]
self.assertIn(current_support_pack, upgrade_guide)
for _, _, _, support_pack in discover_rc_draft_packets():
if support_pack == current_support_pack:
continue
self.assertNotIn(support_pack, upgrade_guide)
self.assertNotIn("docs/releases/V6_RC_OPERATOR_SUPPORT_PACK.md", upgrade_guide)
else:
assert support_packet_paths is not None
release_notes_path, changelog_path = support_packet_paths
self.assertIn(release_notes_path, upgrade_guide)
self.assertIn(changelog_path, upgrade_guide)
for _, _, _, support_pack in discover_rc_draft_packets():
self.assertNotIn(support_pack, upgrade_guide)
self.assertNotIn("docs/releases/V6_RC_OPERATOR_SUPPORT_PACK.md", upgrade_guide)
def test_prerelease_feedback_template_uses_generic_current_rc_wording(self) -> None:
template = read(".github/ISSUE_TEMPLATE/v6_rc_feedback.yml")
@ -855,9 +892,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
if current_version != "6.0.0":
self.assertNotIn(f"VERSION={current_version}", blocked)
else:
self.assertIsNotNone(
rc_packet_paths_for_version(current_version),
f"VERSION={current_version} does not match the 6.0.0-rc.N pattern",
self.assertTrue(
rc_packet_paths_for_version(current_version) is not None
or support_prerelease_packet_paths_for_version(current_version) is not None,
f"VERSION={current_version} does not match a governed v6 prerelease packet pattern",
)
self.assertIn("VERSION=6.0.0", blocked)
self.assertNotIn(f"VERSION={current_version}", blocked)