diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3cf383ed0c..e5553c3ef8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -64,12 +64,21 @@ body: validations: required: true + - type: input + id: previous_version + attributes: + label: Last known working Pulse version + description: If you already observed the same behaviour working, give that version. The version before an upgrade is not necessarily a working baseline. Leave blank or write "unknown" if not known; do not downgrade, restart or repeat the action to find out. + placeholder: v6.x.y or unknown + validations: + required: false + - type: input id: agent_version attributes: label: Agent version - description: Exact agent version (or "none" if no agents are involved). - placeholder: v6.x.y or none + description: Running version of the affected agent, separately from the Pulse server; write "none" for an API-only connection or "unknown" if unavailable. Do not reinstall or re-enrol an agent to find out. + placeholder: v6.x.y, none, or unknown validations: required: true diff --git a/.github/ISSUE_TEMPLATE/v6_rc_feedback.yml b/.github/ISSUE_TEMPLATE/v6_rc_feedback.yml index 16c27903f3..8df1e4bedd 100644 --- a/.github/ISSUE_TEMPLATE/v6_rc_feedback.yml +++ b/.github/ISSUE_TEMPLATE/v6_rc_feedback.yml @@ -33,24 +33,44 @@ body: attributes: label: Pulse version description: Running version for an existing install; for a failed install, the exact version or release asset attempted, or "unknown" if it was not shown. - placeholder: v6.0.0-rc.N, release asset name, or unknown + placeholder: v6.x.y-rc.N, release asset name, or unknown validations: required: true + - type: input + id: previous_version + attributes: + label: Last known working Pulse version + description: If you already observed the same behaviour working, give that version. The version before an upgrade is not necessarily a working baseline. Leave blank or write "unknown" if not known; do not downgrade, restart or repeat the action to find out. + placeholder: v6.x.y or unknown + validations: + required: false + + - type: input + id: agent_version + attributes: + label: Agent version + description: Running version of the affected agent, separately from the Pulse server; write "none" for an API-only connection or "unknown" if unavailable. Do not reinstall or re-enrol an agent to find out. + placeholder: v6.x.y, none, or unknown + validations: + required: false + - type: input id: image_ref attributes: label: Running Pulse container image tag or digest description: For a running Docker, Compose, or Kubernetes container, give its exact Pulse image tag or digest. Leave blank for LXC, bare metal, and failed installs that never started; put an attempted release asset in Pulse version instead. - placeholder: rcourtman/pulse:v6.0.0-rc.N or rcourtman/pulse@sha256:... + placeholder: rcourtman/pulse:v6.x.y-rc.N or rcourtman/pulse@sha256:... - type: dropdown id: install_path attributes: label: Install path + description: How you reached the affected version; an upgrade path alone does not establish that the previous version worked. options: - Clean v6 install - Upgrade from v5 + - Upgrade from a stable v6 release - Upgrade from an earlier v6 prerelease - Not sure validations: @@ -84,7 +104,7 @@ body: id: environment attributes: label: OS / environment - description: Host OS, distro, or environment details that matter to the report. + description: Pulse server OS and, for platform-specific failures, the affected monitored platform and its release where known. Keep these separate from the Pulse server and agent versions; write "unknown" for unavailable details. placeholder: Ubuntu 24.04, Debian 12, Unraid 7, Proxmox 8, macOS 15 validations: required: true diff --git a/.github/scripts/issue-version-triage.test.cjs b/.github/scripts/issue-version-triage.test.cjs index fd38dad10b..5201a05bc4 100644 --- a/.github/scripts/issue-version-triage.test.cjs +++ b/.github/scripts/issue-version-triage.test.cjs @@ -553,3 +553,31 @@ test("version-label removal tolerates an absent label but propagates access fail else await assert.rejects(run, { status: 403 }); } }); + +// Different software versions can coexist in the same upgrade report. Only +// the affected server field may determine the affects-* label. +test("stable-to-preview intake keeps baseline, agent and platform versions out of labels", async () => { + for (const [running, expected] of [ + ["6.5.0-rc.1", "affects-6.5.0-rc.1"], + ["unknown", "needs-version-info"], + ]) { + const { github, calls } = createGithub({ latestVersion: "6.5.0" }); + const issue = { + number: 2400, + title: "[v6 pre-release]: upgraded from 6.4.5", + body: [ + "### Feedback type", "Bug / regression", + "### Pulse version", running, + "### Last known working Pulse version", "6.4.1", + "### Agent version", "6.4.5", + "### Install path", "Upgrade from a stable v6 release", + "### OS / environment", "Debian 12 / SCALE 26.0.0-BETA.3", + "### Additional actionable topics", "None", + ].join("\n\n"), + labels: [], + }; + await triage.syncLabels({ github, context: createContext({ issue }), core: createCore() }); + assert.deepEqual(calls.addLabels.flatMap((call) => call.labels).sort(), [expected, "bug"].sort()); + assert.equal(calls.createComment.length, 0); + } +}); diff --git a/docs/ISSUE_TRIAGE.md b/docs/ISSUE_TRIAGE.md index d10d183f45..22ed9300d7 100644 --- a/docs/ISSUE_TRIAGE.md +++ b/docs/ISSUE_TRIAGE.md @@ -32,6 +32,15 @@ symptom: logs or sanitized diagnostics for connection and data failures, and screenshots or exact error text for visual failures. Do not make a reporter invent logs where none apply. +For upgrades and regressions, keep the affected Pulse server version, agent +version and monitored platform release separate. The pre-release form covers +upgrades from stable v6 as well as earlier previews. Its optional **Last known +working Pulse version** is a baseline only when the reporter observed the same +behaviour working; an upgrade's starting version alone does not establish that. +An API-only connection can have no agent. Accept "unknown" or an omitted optional +field, and use existing evidence; do not ask for a downgrade, restart, reinstall +or re-enrolment to obtain version context. Existing reports need no refile. + Keep the Pulse server's installation separate from the affected target. A Docker-hosted Pulse server can monitor a VM, LXC or NAS; its installation type does not make Docker commands relevant to that target. Read later comments as diff --git a/frontend-modern/public/docs/ISSUE_TRIAGE.md b/frontend-modern/public/docs/ISSUE_TRIAGE.md index d10d183f45..22ed9300d7 100644 --- a/frontend-modern/public/docs/ISSUE_TRIAGE.md +++ b/frontend-modern/public/docs/ISSUE_TRIAGE.md @@ -32,6 +32,15 @@ symptom: logs or sanitized diagnostics for connection and data failures, and screenshots or exact error text for visual failures. Do not make a reporter invent logs where none apply. +For upgrades and regressions, keep the affected Pulse server version, agent +version and monitored platform release separate. The pre-release form covers +upgrades from stable v6 as well as earlier previews. Its optional **Last known +working Pulse version** is a baseline only when the reporter observed the same +behaviour working; an upgrade's starting version alone does not establish that. +An API-only connection can have no agent. Accept "unknown" or an omitted optional +field, and use existing evidence; do not ask for a downgrade, restart, reinstall +or re-enrolment to obtain version context. Existing reports need no refile. + Keep the Pulse server's installation separate from the affected target. A Docker-hosted Pulse server can monitor a VM, LXC or NAS; its installation type does not make Docker commands relevant to that target. Read later comments as diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index 47f729d7bd..6580ef0896 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -1751,11 +1751,52 @@ class ReleasePromotionPolicyTest(unittest.TestCase): def test_prerelease_feedback_template_uses_generic_current_rc_wording(self) -> None: template = read(".github/ISSUE_TEMPLATE/v6_rc_feedback.yml") - self.assertIn("placeholder: v6.0.0-rc.N", template) - self.assertIn("placeholder: rcourtman/pulse:v6.0.0-rc.N or rcourtman/pulse@sha256:...", template) + self.assertIn("placeholder: v6.x.y-rc.N", template) + self.assertIn("placeholder: rcourtman/pulse:v6.x.y-rc.N or rcourtman/pulse@sha256:...", template) self.assertIn("I upgraded to the current v6 RC build", template) self.assertNotIn("v6.0.0-rc.1", template) + def test_prerelease_intake_covers_stable_upgrades_and_separate_agent_versions(self) -> None: + form = yaml.load(read(".github/ISSUE_TEMPLATE/v6_rc_feedback.yml"), Loader=UniqueKeyLoader) + fields = {field["id"]: field for field in form["body"] if "id" in field} + self.assertEqual(len(fields), sum("id" in field for field in form["body"])) + self.assertEqual(fields["install_path"]["attributes"]["options"], [ + "Clean v6 install", "Upgrade from v5", "Upgrade from a stable v6 release", + "Upgrade from an earlier v6 prerelease", "Not sure", + ]) + self.assertTrue(fields["install_path"]["validations"]["required"]) + agent = fields["agent_version"] + self.assertEqual(agent["type"], "input") + self.assertEqual(agent["attributes"]["label"], "Agent version") + self.assertFalse(agent["validations"]["required"]) + for distinction in ("affected agent", "separately from the Pulse server", '"none"', + '"unknown"', "Do not reinstall or re-enrol"): + self.assertIn(distinction, agent["attributes"]["description"]) + self.assertIn("affected monitored platform and its release", fields["environment"]["attributes"]["description"]) + + def test_working_version_context_is_optional_and_never_requires_another_attempt(self) -> None: + for name in ("bug_report.yml", "v6_rc_feedback.yml"): + with self.subTest(form=name): + form = yaml.load(read(f".github/ISSUE_TEMPLATE/{name}"), Loader=UniqueKeyLoader) + fields = {field["id"]: field for field in form["body"] if "id" in field} + self.assertEqual(len(fields), sum("id" in field for field in form["body"])) + previous = fields["previous_version"] + self.assertEqual(previous["type"], "input") + self.assertEqual(previous["attributes"]["label"], "Last known working Pulse version") + self.assertFalse(previous["validations"]["required"]) + for precaution in ("already observed the same behaviour working", + "not necessarily a working baseline", 'write "unknown"', + "do not downgrade, restart or repeat the action"): + self.assertIn(precaution, previous["attributes"]["description"]) + self.assertEqual(fields["pulse_version"]["attributes"]["label"], "Pulse version") + self.assertTrue(fields["pulse_version"]["validations"]["required"]) + self.assertIn('"unknown"', fields["agent_version"]["attributes"]["description"]) + self.assertNotIn("render", previous["attributes"]) + for document in ("docs/ISSUE_TRIAGE.md", "frontend-modern/public/docs/ISSUE_TRIAGE.md"): + triage = normalize_ws(read(document)) + self.assertIn("an upgrade's starting version alone does not establish that", triage) + self.assertIn("do not ask for a downgrade, restart, reinstall or re-enrolment", triage) + def test_report_intake_safety_is_self_contained_for_installed_releases(self) -> None: for name, evidence_id in (("bug_report.yml", "logs"), ("v6_rc_feedback.yml", "evidence")): with self.subTest(form=name):