diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 4f2a547564..72e428e4f0 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -11,6 +11,9 @@ first.** Do not run Diagnostics, force guest-agent probes or repeat a backup to fill a report. A successful backup task or responsive console alone does not establish safe recovery. +- **Not sure which version is affected:** distinguish the + [running server, reported agent and update target](TROUBLESHOOTING.md#find-the-running-server-and-agent-versions). + A helper's selected release or an agent's target is not its running version. - **Cannot sign in:** use [local password recovery](TROUBLESHOOTING.md#i-forgot-my-password) or [401 and login failures](TROUBLESHOOTING.md#cannot-login--401-unauthorized), according to the existing authentication method. Do not delete configuration @@ -72,6 +75,46 @@ valid, and a full configuration, journal or network export is not required. ## ⚡ Quick Fixes +### Find the running server and agent versions + +Use the existing display on the affected Pulse installation; no update, restart +or new diagnostic run is needed just to identify a version. + +- **Pulse server:** open **Settings → System → Updates** and read **Server + version**, not the available update. Keep any beta/RC suffix or development + build identity exactly as displayed. A newer server does not establish that + a separate agent has updated. +- **Pulse Agent:** under **Settings → Infrastructure**, open the affected + connection and read **Pulse Agent version** alongside **Last seen**. An + **Update available** value such as `6.4.5 → 6.5.0` distinguishes the reported + version on the left from the target on the right. **Version target** alone + is not a reported agent version. If the connection is stale or offline, its + last report does not prove what is running now; keep that time and uncertainty. + For an API-only connection with no Pulse Agent, report the agent as **none**, + not the server's version. See [agent update behaviour](UNIFIED_AGENT.md#auto-update) + for the separate update path. +- **Container image:** include the image tag or digest already available in + your deployment manager, separately from **Server version**. A tag such as + `latest` is mutable; a Compose file or selected image is not proof that a + running container was replaced. Keep the exact reference, including the + registry and preview suffix; do not substitute a GitHub release tag. A + `helm-chart-…` release identifies a chart, not a Pulse server archive or the + application version running in Kubernetes. + +Keep the **version at the failure**, an interrupted update's **intended version** +and the **version observed after recovery** separate. Today's display does not +reconstruct the failing run. If the helper selected a release but Pulse never +started, give that attempted release and the public helper name or URL instead; +installer output or a verified download signature does not prove completion. + +If a view is unavailable or you cannot access it with your existing permissions, +write **unknown**; do not widen access, reinstall or re-enrol an agent, rerun an +update or enable Debug to fill the gap. Do not share full container inspections, +resolved Compose configuration, service environments or command lines: they can +contain credentials. Existing version text or a locally reviewed, redacted +excerpt is enough. Add it to the [existing report](#-getting-help); information +already supplied does not need to be collected again. + ### I forgot my password Pulse does not provide an email password-reset flow. Choose the path that @@ -1882,8 +1925,9 @@ If you're still stuck: exact error. Do not repeat an update, outage or notification storm merely to reproduce it. A failed update banner does not prove the action left the target unchanged; check its current state before another attempt. -2. **Identify the affected version**: give the running Pulse and relevant agent - versions, not just the version before an upgrade. For Docker, include the +2. **Identify the affected version**: distinguish the + [running server, reported agent and update target](#find-the-running-server-and-agent-versions), + not just the version before an upgrade. For Docker, include the running image tag or digest. If installation never started Pulse, give the attempted release and public installer/helper source, or say "unknown". 3. **Choose relevant, safe evidence**: use existing logs, errors or observations diff --git a/frontend-modern/public/docs/TROUBLESHOOTING.md b/frontend-modern/public/docs/TROUBLESHOOTING.md index 4f2a547564..72e428e4f0 100644 --- a/frontend-modern/public/docs/TROUBLESHOOTING.md +++ b/frontend-modern/public/docs/TROUBLESHOOTING.md @@ -11,6 +11,9 @@ first.** Do not run Diagnostics, force guest-agent probes or repeat a backup to fill a report. A successful backup task or responsive console alone does not establish safe recovery. +- **Not sure which version is affected:** distinguish the + [running server, reported agent and update target](TROUBLESHOOTING.md#find-the-running-server-and-agent-versions). + A helper's selected release or an agent's target is not its running version. - **Cannot sign in:** use [local password recovery](TROUBLESHOOTING.md#i-forgot-my-password) or [401 and login failures](TROUBLESHOOTING.md#cannot-login--401-unauthorized), according to the existing authentication method. Do not delete configuration @@ -72,6 +75,46 @@ valid, and a full configuration, journal or network export is not required. ## ⚡ Quick Fixes +### Find the running server and agent versions + +Use the existing display on the affected Pulse installation; no update, restart +or new diagnostic run is needed just to identify a version. + +- **Pulse server:** open **Settings → System → Updates** and read **Server + version**, not the available update. Keep any beta/RC suffix or development + build identity exactly as displayed. A newer server does not establish that + a separate agent has updated. +- **Pulse Agent:** under **Settings → Infrastructure**, open the affected + connection and read **Pulse Agent version** alongside **Last seen**. An + **Update available** value such as `6.4.5 → 6.5.0` distinguishes the reported + version on the left from the target on the right. **Version target** alone + is not a reported agent version. If the connection is stale or offline, its + last report does not prove what is running now; keep that time and uncertainty. + For an API-only connection with no Pulse Agent, report the agent as **none**, + not the server's version. See [agent update behaviour](UNIFIED_AGENT.md#auto-update) + for the separate update path. +- **Container image:** include the image tag or digest already available in + your deployment manager, separately from **Server version**. A tag such as + `latest` is mutable; a Compose file or selected image is not proof that a + running container was replaced. Keep the exact reference, including the + registry and preview suffix; do not substitute a GitHub release tag. A + `helm-chart-…` release identifies a chart, not a Pulse server archive or the + application version running in Kubernetes. + +Keep the **version at the failure**, an interrupted update's **intended version** +and the **version observed after recovery** separate. Today's display does not +reconstruct the failing run. If the helper selected a release but Pulse never +started, give that attempted release and the public helper name or URL instead; +installer output or a verified download signature does not prove completion. + +If a view is unavailable or you cannot access it with your existing permissions, +write **unknown**; do not widen access, reinstall or re-enrol an agent, rerun an +update or enable Debug to fill the gap. Do not share full container inspections, +resolved Compose configuration, service environments or command lines: they can +contain credentials. Existing version text or a locally reviewed, redacted +excerpt is enough. Add it to the [existing report](#-getting-help); information +already supplied does not need to be collected again. + ### I forgot my password Pulse does not provide an email password-reset flow. Choose the path that @@ -1882,8 +1925,9 @@ If you're still stuck: exact error. Do not repeat an update, outage or notification storm merely to reproduce it. A failed update banner does not prove the action left the target unchanged; check its current state before another attempt. -2. **Identify the affected version**: give the running Pulse and relevant agent - versions, not just the version before an upgrade. For Docker, include the +2. **Identify the affected version**: distinguish the + [running server, reported agent and update target](#find-the-running-server-and-agent-versions), + not just the version before an upgrade. For Docker, include the running image tag or digest. If installation never started Pulse, give the attempted release and public installer/helper source, or say "unknown". 3. **Choose relevant, safe evidence**: use existing logs, errors or observations diff --git a/scripts/tests/test_running_version_help_docs.py b/scripts/tests/test_running_version_help_docs.py new file mode 100644 index 0000000000..4e03472c5c --- /dev/null +++ b/scripts/tests/test_running_version_help_docs.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Keep passive version help tied to the real current/report/target displays.""" + +from pathlib import Path +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +HEADING = "### Find the running server and agent versions\n" +ANCHOR = "#find-the-running-server-and-agent-versions" + + +class RunningVersionHelpDocsTest(unittest.TestCase): + def setUp(self): + self.source = (ROOT / "docs/TROUBLESHOOTING.md").read_text() + self.guide = " ".join( + self.source.split(HEADING, 1)[1].split("\n### ", 1)[0].split() + ) + + def test_workflow_entry_and_getting_help_reach_the_same_guidance(self): + self.assertIn(ANCHOR, self.source.split("## ⚡ Quick Fixes", 1)[0]) + self.assertIn(ANCHOR, self.source.split("## 🆘 Getting Help", 1)[1]) + self.assertEqual(self.source.count(HEADING), 1) + + def test_server_instruction_names_the_running_field_not_the_update(self): + copy = (ROOT / "frontend-modern/src/utils/updatesPresentation.ts").read_text() + panel = (ROOT / "frontend-modern/src/components/Settings/UpdatesSettingsPanel.tsx").read_text() + self.assertIn("currentVersionLabel: 'Server version'", copy) + self.assertIn("{props.versionInfo()?.version || 'Loading...'}", panel) + self.assertIn("**Server version**, not the available update", self.guide) + self.assertIn("Keep any beta/RC suffix or development build identity", self.guide) + self.assertIn("A newer server does not establish", self.guide) + + def test_agent_instruction_distinguishes_last_report_from_target(self): + model = (ROOT / "frontend-modern/src/components/Settings/connectionsTableModel.ts").read_text() + workspace = (ROOT / "frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx").read_text() + self.assertIn("connection.agentVersion?.trim()", model) + self.assertIn("connection.expectedAgentVersion?.trim()", model) + self.assertIn("detail: `${currentVersion} -> ${expectedVersion}`", model) + self.assertIn("badgeLabel: 'Version target'", model) + self.assertIn("Pulse Agent version", workspace) + self.assertIn("infoCard('Last seen'", workspace) + for fact in ( + "reported version on the left", "target on the right", + "**Version target** alone is not a reported agent version", + "last report does not prove what is running now", + "agent as **none**, not the server's version", + "UNIFIED_AGENT.md#auto-update", + ): + with self.subTest(fact=fact): + self.assertIn(fact, self.guide) + + def test_image_helper_and_recovery_are_not_treated_as_running_source(self): + for fact in ( + "`latest` is mutable", "not proof that a running container was replaced", + "do not substitute a GitHub release tag", "`helm-chart-…`", + "not a Pulse server archive", "**version at the failure**", + "**intended version**", "**version observed after recovery**", + "Today's display does not reconstruct the failing run", + "verified download signature does not prove completion", + ): + with self.subTest(fact=fact): + self.assertIn(fact, self.guide) + + def test_unknown_is_valid_without_mutation_or_private_exports(self): + self.assertNotIn("```", self.guide) + for boundary in ( + "write **unknown**", "do not widen access, reinstall or re-enrol", + "rerun an update or enable Debug", "Do not share full container inspections", + "resolved Compose configuration", "service environments or command lines", + "locally reviewed, redacted excerpt", "already supplied does not need", + ): + with self.subTest(boundary=boundary): + self.assertIn(boundary, self.guide) + + def test_shipped_help_is_byte_identical(self): + self.assertEqual( + (ROOT / "docs/TROUBLESHOOTING.md").read_bytes(), + (ROOT / "frontend-modern/public/docs/TROUBLESHOOTING.md").read_bytes(), + ) + + +if __name__ == "__main__": + unittest.main()