diff --git a/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md b/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md index c814b739b3..967b72c862 100644 --- a/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md +++ b/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md @@ -56,6 +56,21 @@ Cloud, and self-hosted production users. 3. Failed prereleases are fixed forward and replaced with a new prerelease. They are never promoted as-is to `stable`. +## Paid Pro Artifact Lineage + +1. Customer-facing private Pulse Pro archives and private Pulse Pro Docker images + must track the same immutable release checkpoint as the public Pulse release + they support. +2. During the v6 RC phase, private Pro artifacts must be built from the exact + public RC tag, use the same RC version, and publish under RC-shaped artifact + names, R2 prefixes, and Docker tags such as `6.0.0-rc.5`. +3. Do not build or advertise `license.pulserelay.pro/pulse-pro:6.0.0`, a + `pulse-pro-v6.0.0-...` private archive, or a GA-shaped private R2 prefix + until the intentional v6 GA publish. +4. A private Pro build from a moving branch is valid only as an internal proof + artifact. It is not valid customer guidance and must not update the live + paid-download manifest or private Docker customer tag. + ## v5 Maintenance Policy 1. When Pulse v6 reaches `stable`, Pulse v5 immediately enters diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index e9c2606a72..11b22dd79e 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -218,19 +218,26 @@ server-side update execution surfaces. declared feature-complete. Paid-user GA is part of that same release boundary: the public Pulse release workflow builds OSS `pulse-v...` artifacts only, so release docs and runbooks - must require a same-ref/same-version `pulse-enterprise` Pro package, verify - `pulse-pro-v...` archives identify `Pulse Pro`, and keep the paid - install/upgrade path pointed at Pro artifacts or a verified paid image before - any paid-user Pro runtime claim is made. Public GitHub release assets and the + must require a same-tag/same-version `pulse-enterprise` Pro package for + customer-facing publication, verify `pulse-pro-v...` archives identify + `Pulse Pro`, and keep the paid install/upgrade path pointed at Pro artifacts + or a verified paid image before any paid-user Pro runtime claim is made. + During the v6 RC phase, private Pulse Pro archive prefixes and Docker tags + must retain the RC suffix from the exact public Pulse RC tag; GA-shaped + `6.0.0` Pro archive names, R2 prefixes, and Docker tags are reserved for the + intentional v6 GA publish. Public GitHub release assets and the public `rcourtman/pulse` Docker image must be described as community builds where paid customers are likely to install or upgrade, and generated public release bodies must send Relay, Pulse Pro, and eligible legacy customers to `https://pulserelay.pro/download.html` for the private Pulse Pro Docker image - or Linux archive. Public Docker and install docs must also preserve a + or Linux/LXC archive. Public Docker and install docs must also preserve a `PULSE_IMAGE`-aware compose image line and warn that any hardcoded `image: rcourtman/pulse:...` line must be replaced before the private Pulse Pro compose commands can move an existing Docker install off the - community image. + community image. The root installer must accept private `pulse-pro-v...` + archive filenames through `--archive` so direct Linux and Proxmox LXC users + can keep the normal service setup while installing the private Pulse Pro + runtime. The repo-root VERSION file is part of the same governed boundary and must not drift as an unowned release-cut switch: changing the version string for a new RC or diff --git a/install.sh b/install.sh index f48474eaee..275ba8ad27 100755 --- a/install.sh +++ b/install.sh @@ -2544,8 +2544,8 @@ infer_release_from_archive_name() { local archive_name archive_name=$(basename "$1") - if [[ "$archive_name" =~ ^pulse-(v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)*)-linux-(amd64|arm64|armv7)\.tar\.gz$ ]]; then - printf '%s\n' "${BASH_REMATCH[1]}" + if [[ "$archive_name" =~ ^pulse(-pro)?-(v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)*)-linux-(amd64|arm64|armv7|armv6|386)\.tar\.gz$ ]]; then + printf '%s\n' "${BASH_REMATCH[2]}" return 0 fi diff --git a/scripts/installtests/root_install_sh_test.go b/scripts/installtests/root_install_sh_test.go index 54b9bf3f05..7f3619730f 100644 --- a/scripts/installtests/root_install_sh_test.go +++ b/scripts/installtests/root_install_sh_test.go @@ -88,6 +88,32 @@ func TestRootInstallScriptArchiveSupportContract(t *testing.T) { } } +func TestRootInstallScriptInfersPrivateProArchiveVersion(t *testing.T) { + script := ` + set -euo pipefail +` + extractRootInstallShellFunction(t, "infer_release_from_archive_name") + ` + infer_release_from_archive_name /tmp/pulse-v6.0.0-rc.5-linux-amd64.tar.gz + infer_release_from_archive_name /tmp/pulse-pro-v6.0.0-rc.5-linux-amd64.tar.gz + infer_release_from_archive_name /tmp/pulse-pro-v6.0.0-linux-arm64.tar.gz + ` + + out, err := exec.Command("bash", "-c", script).CombinedOutput() + if err != nil { + t.Fatalf("bash: %v\n%s", err, out) + } + + got := strings.Fields(string(out)) + want := []string{"v6.0.0-rc.5", "v6.0.0-rc.5", "v6.0.0"} + if len(got) != len(want) { + t.Fatalf("versions = %#v, want %#v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("versions = %#v, want %#v", got, want) + } + } +} + func TestRootInstallScriptUpdateDiskHeadroomRejectsSharedLowSpaceFilesystem(t *testing.T) { script := ` set -euo pipefail diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index 52cdbd76fe..20d59ab9df 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -88,6 +88,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("release notes may keep placeholder", content) self.assertIn("Exact v6 GA and v5 end-of-support dates locked before GA publish", content) self.assertIn("governed prerelease and stable release branches", content) + self.assertIn("Customer-facing private Pulse Pro archives", content) + self.assertIn("public RC tag", content) + self.assertIn("license.pulserelay.pro/pulse-pro:6.0.0", content) + self.assertIn("moving branch", content) def test_pre_release_checklist_tracks_rc_to_ga_gate_inputs(self) -> None: content = read("docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md") @@ -355,7 +359,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): "the public Pulse release workflow builds OSS `pulse-v...` artifacts only", normalize_ws(contract), ) - self.assertIn("`pulse-pro-v...` archives identify `Pulse Pro`", contract) + self.assertIn("`pulse-pro-v...` archives identify `Pulse Pro`", normalize_ws(contract)) self.assertIn("https://pulserelay.pro/download.html", contract) self.assertIn("PULSE_IMAGE`-aware compose image line", normalize_ws(contract)) self.assertIn("hardcoded `image: rcourtman/pulse:...`", normalize_ws(contract)) diff --git a/scripts/release_control/render_release_body.py b/scripts/release_control/render_release_body.py index 3d26f8f06f..027cab9f7c 100644 --- a/scripts/release_control/render_release_body.py +++ b/scripts/release_control/render_release_body.py @@ -81,7 +81,7 @@ def build_installation_section(version: str) -> str: "", "See the [Installation Guide](https://github.com/rcourtman/Pulse#installation) for complete setup instructions.", "", - "Paid Pulse Pro, Relay, and eligible legacy customers: public GitHub release assets and the public `rcourtman/pulse` Docker image are community builds. They do not include the private Pulse Pro runtime hooks. Use https://pulserelay.pro/download.html with your activation key to get the private Pulse Pro Docker image or Linux archive.", + "Paid Pulse Pro, Relay, and eligible legacy customers: public GitHub release assets and the public `rcourtman/pulse` Docker image are community builds. They do not include the private Pulse Pro runtime hooks. Use https://pulserelay.pro/download.html with your activation key to get the private Pulse Pro Docker image or Linux/LXC archive.", ] )