mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Reframe prerelease update copy
This commit is contained in:
@@ -111,6 +111,11 @@ that promotion metadata. Human-visible workflow inputs, summaries, and error
|
||||
messages must describe the path as a prerelease or preview flow rather than
|
||||
implying a near-ready release candidate, while machine-owned identifiers such
|
||||
as `rc`, `rc-to-ga-*`, and `v6.0.0-rc.1` remain the canonical internal keys.
|
||||
That same prerelease framing requirement also applies to installer and update
|
||||
runtime copy: `install.sh`, `scripts/pulse-auto-update.sh`, and
|
||||
`internal/updates/manager.go` must present `rc`-tagged builds as prerelease or
|
||||
preview paths in menus, operator diagnostics, and runtime logs rather than as
|
||||
release-candidate promises.
|
||||
Those same workflows must also fetch and dispatch the governed release branch
|
||||
derived from release-control metadata instead of hardcoding `pulse/v6`,
|
||||
`pulse/v6-release`, or any later branch literal inline.
|
||||
@@ -190,6 +195,10 @@ That same managed browser proof pack must also keep the desktop Recovery page
|
||||
layout guard on the canonical entrypoint, so `dev:verify` catches right-edge
|
||||
history-table overflow regressions introduced by more human-readable subject
|
||||
labels instead of leaving that check as a hidden one-off Playwright command.
|
||||
That same proof pack must also keep the Patrol blocked-runtime page contract on
|
||||
the canonical entrypoint, so `dev:verify` catches stale healthy-summary
|
||||
regressions where the real `/ai` route would otherwise look healthy even after
|
||||
the backend reports `runtime_state=blocked`.
|
||||
That same launcher boundary now also owns the one-command verification entry
|
||||
point for that proof. `./scripts/hot-dev-bg.sh verify` must prepare a coherent
|
||||
managed runtime, run the canonical browser recovery proof with the managed dev
|
||||
@@ -257,9 +266,10 @@ and restart it when the owner process exits unexpectedly, so a killed or wedged
|
||||
foreground owner does not leave both `5173` and `7655` down until a human
|
||||
notices.
|
||||
That self-healing guarantee must be covered by the canonical managed browser
|
||||
proof pack as well: `dev:verify` must prove both backend-bounce recovery and
|
||||
owner-process-death recovery on the browser entrypoint, rather than leaving the
|
||||
supervision contract to shell-only smoke tests.
|
||||
proof pack as well: `dev:verify` must prove backend-bounce recovery,
|
||||
owner-process-death recovery, and the Patrol blocked-runtime page contract on
|
||||
the browser entrypoint, rather than leaving supervision and Patrol-shell drift
|
||||
to shell-only smoke tests.
|
||||
The same wrapper-first rule applies to launcher help text: `hot-dev-bg` usage
|
||||
output must present the repo-root npm entrypoints first and reserve raw
|
||||
subcommands as secondary script-local controls for direct troubleshooting.
|
||||
|
||||
+4
-4
@@ -2133,13 +2133,13 @@ download_pulse() {
|
||||
CONFIGURED_CHANNEL=$(cat "$CONFIG_DIR/system.json" 2>/dev/null | grep -o '"updateChannel"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)"$/\1/' || true)
|
||||
if [[ "$CONFIGURED_CHANNEL" == "rc" ]]; then
|
||||
UPDATE_CHANNEL="rc"
|
||||
print_info "RC channel detected in configuration"
|
||||
print_info "Prerelease channel detected in configuration"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get appropriate release based on channel (with timeout)
|
||||
# Both stable and RC channels now use /releases endpoint to handle draft releases
|
||||
# Both stable and prerelease channels now use /releases endpoint to handle draft releases
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
RELEASES_JSON=$(timeout 15 curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$GITHUB_REPO/releases 2>/dev/null || true)
|
||||
else
|
||||
@@ -2148,7 +2148,7 @@ download_pulse() {
|
||||
|
||||
if [[ -n "$RELEASES_JSON" ]]; then
|
||||
if [[ "$UPDATE_CHANNEL" == "rc" ]]; then
|
||||
# RC channel: Get latest release (including pre-releases, but skip drafts)
|
||||
# Prerelease channel: get latest release (including prereleases, but skip drafts)
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
LATEST_RELEASE=$(echo "$RELEASES_JSON" | jq -r '[.[] | select(.draft == false)][0].tag_name' 2>/dev/null || true)
|
||||
else
|
||||
@@ -3428,7 +3428,7 @@ main() {
|
||||
fi
|
||||
|
||||
if [[ -n "$RC_VERSION" ]] && [[ "$RC_VERSION" != "$STABLE_VERSION" ]] && [[ "$RC_VERSION" != "$CURRENT_VERSION" ]]; then
|
||||
echo "${menu_option}) Update to $RC_VERSION (release candidate)"
|
||||
echo "${menu_option}) Update to $RC_VERSION (prerelease preview)"
|
||||
((menu_option++))
|
||||
fi
|
||||
|
||||
|
||||
+10
-10
@@ -714,12 +714,12 @@ func (m *Manager) getLatestReleaseForChannel(ctx context.Context, channel string
|
||||
}
|
||||
|
||||
// Find latest release based on channel
|
||||
// RC channel: return newest release (RC or stable), even if not newer than current
|
||||
// Prerelease channel: return newest release (prerelease or stable), even if not newer than current
|
||||
// Stable channel: return newest stable release, even if not newer than current
|
||||
// The caller will determine if it's actually an update by comparing versions
|
||||
if channel == "rc" {
|
||||
// For RC channel: find newest release (RC or stable)
|
||||
// RC users should see both RCs and stable releases
|
||||
// For the prerelease channel: find newest release (prerelease or stable)
|
||||
// Prerelease users should see both prereleases and stable releases
|
||||
var newestRC *ReleaseInfo
|
||||
var newestStable *ReleaseInfo
|
||||
|
||||
@@ -737,7 +737,7 @@ func (m *Manager) getLatestReleaseForChannel(ctx context.Context, channel string
|
||||
}
|
||||
|
||||
if releases[i].Prerelease {
|
||||
// Track newest RC
|
||||
// Track newest prerelease
|
||||
if newestRC == nil {
|
||||
newestRC = &releases[i]
|
||||
} else {
|
||||
@@ -767,7 +767,7 @@ func (m *Manager) getLatestReleaseForChannel(ctx context.Context, channel string
|
||||
if stableVer.IsNewerThan(rcVer) {
|
||||
isUpdate := stableVer.IsNewerThan(currentVer)
|
||||
if isUpdate {
|
||||
log.Info().Str("version", newestStable.TagName).Msg("Found stable update for RC user")
|
||||
log.Info().Str("version", newestStable.TagName).Msg("Found stable update for prerelease user")
|
||||
} else {
|
||||
log.Info().Str("version", newestStable.TagName).Msg("On latest stable version")
|
||||
}
|
||||
@@ -775,15 +775,15 @@ func (m *Manager) getLatestReleaseForChannel(ctx context.Context, channel string
|
||||
}
|
||||
isUpdate := rcVer.IsNewerThan(currentVer)
|
||||
if isUpdate {
|
||||
log.Info().Str("version", newestRC.TagName).Msg("Found RC update")
|
||||
log.Info().Str("version", newestRC.TagName).Msg("Found prerelease update")
|
||||
} else {
|
||||
log.Info().Str("version", newestRC.TagName).Msg("On latest RC version")
|
||||
log.Info().Str("version", newestRC.TagName).Msg("On latest prerelease version")
|
||||
}
|
||||
return newestRC, nil
|
||||
} else if newestStable != nil {
|
||||
isUpdate := newestStable.TagName != currentVer.String()
|
||||
if isUpdate {
|
||||
log.Info().Str("version", newestStable.TagName).Msg("Found stable update for RC user")
|
||||
log.Info().Str("version", newestStable.TagName).Msg("Found stable update for prerelease user")
|
||||
} else {
|
||||
log.Info().Str("version", newestStable.TagName).Msg("On latest stable version")
|
||||
}
|
||||
@@ -791,9 +791,9 @@ func (m *Manager) getLatestReleaseForChannel(ctx context.Context, channel string
|
||||
} else if newestRC != nil {
|
||||
isUpdate := newestRC.TagName != currentVer.String()
|
||||
if isUpdate {
|
||||
log.Info().Str("version", newestRC.TagName).Msg("Found RC update")
|
||||
log.Info().Str("version", newestRC.TagName).Msg("Found prerelease update")
|
||||
} else {
|
||||
log.Info().Str("version", newestRC.TagName).Msg("On latest RC version")
|
||||
log.Info().Str("version", newestRC.TagName).Msg("On latest prerelease version")
|
||||
}
|
||||
return newestRC, nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func mockGitHubReleases(releases []ReleaseInfo) *httptest.Server {
|
||||
}))
|
||||
}
|
||||
|
||||
func TestRCUpdateNotifications(t *testing.T) {
|
||||
func TestPrereleaseUpdateNotifications(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
currentVersion string
|
||||
@@ -52,7 +52,7 @@ func TestRCUpdateNotifications(t *testing.T) {
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "RC user with newer RC available",
|
||||
name: "prerelease user with newer prerelease available",
|
||||
currentVersion: "4.22.0-rc.1",
|
||||
releases: []ReleaseInfo{
|
||||
{TagName: "v4.22.0-rc.3", Prerelease: true},
|
||||
@@ -61,10 +61,10 @@ func TestRCUpdateNotifications(t *testing.T) {
|
||||
},
|
||||
expectedVersion: "v4.22.0-rc.3",
|
||||
expectUpdate: true,
|
||||
description: "RC users should see newer RC releases",
|
||||
description: "Prerelease users should see newer prerelease releases",
|
||||
},
|
||||
{
|
||||
name: "RC user with newer stable available",
|
||||
name: "prerelease user with newer stable available",
|
||||
currentVersion: "4.22.0-rc.3",
|
||||
releases: []ReleaseInfo{
|
||||
{TagName: "v4.22.0", Prerelease: false},
|
||||
@@ -73,10 +73,10 @@ func TestRCUpdateNotifications(t *testing.T) {
|
||||
},
|
||||
expectedVersion: "v4.22.0",
|
||||
expectUpdate: true,
|
||||
description: "RC users should see newer stable releases (stable > RC for same version)",
|
||||
description: "Prerelease users should see newer stable releases (stable > prerelease for the same version)",
|
||||
},
|
||||
{
|
||||
name: "RC user with both newer RC and stable (stable wins)",
|
||||
name: "prerelease user with both newer prerelease and stable (stable wins)",
|
||||
currentVersion: "4.22.0-rc.1",
|
||||
releases: []ReleaseInfo{
|
||||
{TagName: "v4.23.0-rc.1", Prerelease: true},
|
||||
@@ -86,10 +86,10 @@ func TestRCUpdateNotifications(t *testing.T) {
|
||||
},
|
||||
expectedVersion: "v4.23.0-rc.1",
|
||||
expectUpdate: true,
|
||||
description: "When both RC and stable are available, return the highest version",
|
||||
description: "When both a prerelease and stable release are available, return the highest version",
|
||||
},
|
||||
{
|
||||
name: "RC user with only older releases",
|
||||
name: "prerelease user with only older releases",
|
||||
currentVersion: "4.23.0-rc.1",
|
||||
releases: []ReleaseInfo{
|
||||
{TagName: "v4.22.0", Prerelease: false},
|
||||
@@ -97,10 +97,10 @@ func TestRCUpdateNotifications(t *testing.T) {
|
||||
},
|
||||
expectedVersion: "v4.22.0",
|
||||
expectUpdate: true, // Returns latest version even if not newer (Available will be false)
|
||||
description: "Returns latest stable version even when user is on newer RC",
|
||||
description: "Returns the latest stable version even when the user is on a newer prerelease",
|
||||
},
|
||||
{
|
||||
name: "RC user already on latest stable",
|
||||
name: "prerelease user already on latest stable",
|
||||
currentVersion: "4.22.0-rc.1",
|
||||
releases: []ReleaseInfo{
|
||||
{TagName: "v4.22.0", Prerelease: false},
|
||||
@@ -108,7 +108,7 @@ func TestRCUpdateNotifications(t *testing.T) {
|
||||
},
|
||||
expectedVersion: "v4.22.0",
|
||||
expectUpdate: true,
|
||||
description: "RC user should see stable release even if RC number is same (4.22.0 > 4.22.0-rc.1)",
|
||||
description: "A prerelease user should see the stable release even when the prerelease number matches (4.22.0 > 4.22.0-rc.1)",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -152,6 +152,36 @@ func TestRCUpdateNotifications(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrereleaseUpdateLogCopy(t *testing.T) {
|
||||
content, err := os.ReadFile("manager.go")
|
||||
if err != nil {
|
||||
t.Fatalf("read manager.go: %v", err)
|
||||
}
|
||||
|
||||
source := string(content)
|
||||
required := []string{
|
||||
`Found stable update for prerelease user`,
|
||||
`Found prerelease update`,
|
||||
`On latest prerelease version`,
|
||||
}
|
||||
for _, needle := range required {
|
||||
if !strings.Contains(source, needle) {
|
||||
t.Fatalf("manager.go missing prerelease runtime log fragment: %s", needle)
|
||||
}
|
||||
}
|
||||
|
||||
forbidden := []string{
|
||||
`Found stable update for RC user`,
|
||||
`Found RC update`,
|
||||
`On latest RC version`,
|
||||
}
|
||||
for _, needle := range forbidden {
|
||||
if strings.Contains(source, needle) {
|
||||
t.Fatalf("manager.go preserved stale release-candidate runtime log fragment: %s", needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStableUpdateNotifications(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -113,3 +113,44 @@ func TestRootInstallScriptAutoRegisterUsesSecureContractShape(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrereleaseUpdateCopyUsesPreviewFraming(t *testing.T) {
|
||||
rootInstall, err := os.ReadFile(filepath.Join("..", "..", "install.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read root install.sh: %v", err)
|
||||
}
|
||||
|
||||
installScript := string(rootInstall)
|
||||
requiredInstall := []string{
|
||||
`Update to $RC_VERSION (prerelease preview)`,
|
||||
`Prerelease channel detected in configuration`,
|
||||
`Prerelease channel: get latest release (including prereleases, but skip drafts)`,
|
||||
}
|
||||
for _, needle := range requiredInstall {
|
||||
if !strings.Contains(installScript, needle) {
|
||||
t.Fatalf("root install.sh missing prerelease framing fragment: %s", needle)
|
||||
}
|
||||
}
|
||||
forbiddenInstall := []string{
|
||||
`Update to $RC_VERSION (release candidate)`,
|
||||
`RC channel detected in configuration`,
|
||||
`RC channel: Get latest release (including pre-releases, but skip drafts)`,
|
||||
}
|
||||
for _, needle := range forbiddenInstall {
|
||||
if strings.Contains(installScript, needle) {
|
||||
t.Fatalf("root install.sh preserved stale release-candidate framing fragment: %s", needle)
|
||||
}
|
||||
}
|
||||
|
||||
autoUpdate, err := os.ReadFile(filepath.Join("..", "..", "scripts", "pulse-auto-update.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read pulse-auto-update.sh: %v", err)
|
||||
}
|
||||
autoUpdateScript := string(autoUpdate)
|
||||
if !strings.Contains(autoUpdateScript, `Prerelease channel detected; unattended auto-updates run only on stable`) {
|
||||
t.Fatalf("pulse-auto-update.sh missing prerelease channel log message")
|
||||
}
|
||||
if strings.Contains(autoUpdateScript, `RC channel detected; unattended auto-updates run only on stable`) {
|
||||
t.Fatalf("pulse-auto-update.sh preserved stale release-candidate channel log message")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ check_auto_updates_enabled() {
|
||||
exit 0
|
||||
fi
|
||||
if [[ "$channel" == "rc" ]]; then
|
||||
log info "RC channel detected; unattended auto-updates run only on stable"
|
||||
log info "Prerelease channel detected; unattended auto-updates run only on stable"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user