mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 05:29:45 +00:00
Align PMG collection guidance with saved-scope repair
Describe the integrated collector gates, explicit opt-out readback, legacy mail defaults and next-poll limits without treating dataset switches as a connection pause. Retain the verified v6.5.0 warning and incomplete-reading/alert safeguards in the existing guide and shipped mirror. Contract-Neutral: Documentation and source-bound guidance tests only; no runtime, API, schema, permission or navigation change. Change-source: pulse-maintainer
This commit is contained in:
+33
-5
@@ -37,14 +37,35 @@ by itself, and a missing candidate does not prove the gateway is unavailable.
|
||||
Only scan networks you administer and intend to scan. Do not broaden a scan or
|
||||
repeat discovery merely to diagnose missing statistics.
|
||||
|
||||
### Collection scope is not a no-request guarantee
|
||||
### Collection scope and older releases
|
||||
|
||||
The connection form offers **Mail statistics & trends**, **Queue health
|
||||
insights**, **Quarantine totals** and **Domain-level statistics**. Domain
|
||||
statistics are off by default for a new connection. Do not use the individual
|
||||
scope switches as a privacy or no-request boundary: mail, queue and quarantine
|
||||
requests can still run with their options off. A saved checkbox or an absent
|
||||
chart is not evidence that those requests stopped.
|
||||
statistics are off by default for a new connection. In current development
|
||||
source, the saved switches control these subsequent ordinary poll requests:
|
||||
|
||||
| Saved option | Reads it controls |
|
||||
| --- | --- |
|
||||
| Mail statistics & trends | Mail statistics, hourly mail counts and spam-score distribution. |
|
||||
| Queue health insights | Each collected cluster node's queue status. |
|
||||
| Quarantine totals | Both spam and virus quarantine counts. |
|
||||
| Domain-level statistics | Relay-domain inventory and domain statistics. |
|
||||
|
||||
Saving all four options off preserves that choice when settings are reopened.
|
||||
An older connection with no recorded scope keeps the legacy mail-statistics
|
||||
default until its collection scope is explicitly saved; all-zero legacy settings
|
||||
cannot distinguish an old opt-out from an unset default. Version, cluster and
|
||||
configuration-backup reads are independent of these four switches: all four off
|
||||
does not mean no PMG requests. Edits apply to the next poll, not requests already
|
||||
in flight. Disabling collection removes coverage; it does not dismiss an
|
||||
existing alert or establish recovery.
|
||||
|
||||
**Published v6.5.0 does not contain this scope correction.** In that version,
|
||||
mail, queue and quarantine requests can still run with their options off;
|
||||
domain statistics alone follow their switch. Do not rely on those three
|
||||
switches as a privacy boundary in v6.5.0, or assume development-source behaviour
|
||||
is present in an installed release. An absent chart is not evidence that
|
||||
requests stopped.
|
||||
|
||||
If PMG collection must stop, pause the **PMG connection** in
|
||||
**Settings → Infrastructure**, rather than deleting it or only disabling
|
||||
@@ -77,6 +98,13 @@ badge or one populated panel does not prove that every dataset was read. Compare
|
||||
the affected value and observation window with PMG's own existing view before
|
||||
acting on an apparent empty queue or quarantine.
|
||||
|
||||
Current development source records downstream read failures as incomplete
|
||||
collection while retaining the successful version connection. Missing readings
|
||||
do not become observed zero totals or resolve their metric alerts. Partial
|
||||
queue data can still raise the highest configured severity, but cannot clear
|
||||
or downgrade an existing alert; quarantine recovery requires both categories.
|
||||
These collector safeguards do not make every empty display a verified zero.
|
||||
|
||||
## Alerts
|
||||
|
||||
Open **Alerts → Thresholds → Proxmox** and find **Mail Gateway Thresholds**.
|
||||
|
||||
@@ -37,14 +37,35 @@ by itself, and a missing candidate does not prove the gateway is unavailable.
|
||||
Only scan networks you administer and intend to scan. Do not broaden a scan or
|
||||
repeat discovery merely to diagnose missing statistics.
|
||||
|
||||
### Collection scope is not a no-request guarantee
|
||||
### Collection scope and older releases
|
||||
|
||||
The connection form offers **Mail statistics & trends**, **Queue health
|
||||
insights**, **Quarantine totals** and **Domain-level statistics**. Domain
|
||||
statistics are off by default for a new connection. Do not use the individual
|
||||
scope switches as a privacy or no-request boundary: mail, queue and quarantine
|
||||
requests can still run with their options off. A saved checkbox or an absent
|
||||
chart is not evidence that those requests stopped.
|
||||
statistics are off by default for a new connection. In current development
|
||||
source, the saved switches control these subsequent ordinary poll requests:
|
||||
|
||||
| Saved option | Reads it controls |
|
||||
| --- | --- |
|
||||
| Mail statistics & trends | Mail statistics, hourly mail counts and spam-score distribution. |
|
||||
| Queue health insights | Each collected cluster node's queue status. |
|
||||
| Quarantine totals | Both spam and virus quarantine counts. |
|
||||
| Domain-level statistics | Relay-domain inventory and domain statistics. |
|
||||
|
||||
Saving all four options off preserves that choice when settings are reopened.
|
||||
An older connection with no recorded scope keeps the legacy mail-statistics
|
||||
default until its collection scope is explicitly saved; all-zero legacy settings
|
||||
cannot distinguish an old opt-out from an unset default. Version, cluster and
|
||||
configuration-backup reads are independent of these four switches: all four off
|
||||
does not mean no PMG requests. Edits apply to the next poll, not requests already
|
||||
in flight. Disabling collection removes coverage; it does not dismiss an
|
||||
existing alert or establish recovery.
|
||||
|
||||
**Published v6.5.0 does not contain this scope correction.** In that version,
|
||||
mail, queue and quarantine requests can still run with their options off;
|
||||
domain statistics alone follow their switch. Do not rely on those three
|
||||
switches as a privacy boundary in v6.5.0, or assume development-source behaviour
|
||||
is present in an installed release. An absent chart is not evidence that
|
||||
requests stopped.
|
||||
|
||||
If PMG collection must stop, pause the **PMG connection** in
|
||||
**Settings → Infrastructure**, rather than deleting it or only disabling
|
||||
@@ -77,6 +98,13 @@ badge or one populated panel does not prove that every dataset was read. Compare
|
||||
the affected value and observation window with PMG's own existing view before
|
||||
acting on an apparent empty queue or quarantine.
|
||||
|
||||
Current development source records downstream read failures as incomplete
|
||||
collection while retaining the successful version connection. Missing readings
|
||||
do not become observed zero totals or resolve their metric alerts. Partial
|
||||
queue data can still raise the highest configured severity, but cannot clear
|
||||
or downgrade an existing alert; quarantine recovery requires both categories.
|
||||
These collector safeguards do not make every empty display a verified zero.
|
||||
|
||||
## Alerts
|
||||
|
||||
Open **Alerts → Thresholds → Proxmox** and find **Mail Gateway Thresholds**.
|
||||
|
||||
@@ -22,6 +22,17 @@ def plain(text):
|
||||
return " ".join(text.replace("**", "").split())
|
||||
|
||||
|
||||
def go_block(text, opening):
|
||||
"""Read one known source block, including its nested bodies."""
|
||||
start = text.index(opening) + len(opening)
|
||||
depth = 1
|
||||
for end in range(start, len(text)):
|
||||
depth += (text[end] == "{") - (text[end] == "}")
|
||||
if depth == 0:
|
||||
return text[start:end]
|
||||
raise AssertionError(f"Unclosed source block: {opening}")
|
||||
|
||||
|
||||
class PMGDocsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.text = DOC.read_text(encoding="utf-8")
|
||||
@@ -95,22 +106,78 @@ class PMGDocsTest(unittest.TestCase):
|
||||
self.assertIn("does not prove that every dataset was read", self.guide)
|
||||
self.assertIn("Unavailable evidence is unknown, not zero", self.guide)
|
||||
|
||||
def test_opt_out_guidance_does_not_invent_scope_enforcement(self):
|
||||
def test_saved_scope_guidance_matches_each_owning_gate(self):
|
||||
poll = (ROOT / "internal/monitoring/monitor_pbs_pmg.go").read_text()
|
||||
poll = poll.split("func (m *Monitor) pollPMGInstance(", 1)[1]
|
||||
# Do not lock in the runtime defect. If the owner adds these gates,
|
||||
# the caveat can be reviewed/removed with that containing correction.
|
||||
if any(f"if instanceCfg.{flag}" not in poll for flag in (
|
||||
"MonitorMailStats", "MonitorQueues", "MonitorQuarantine",
|
||||
)):
|
||||
self.assertIn("requests can still run with their options off", self.guide)
|
||||
self.assertIn("not evidence that those requests stopped", self.guide)
|
||||
for condition, methods, description in (
|
||||
("MailStatsEnabled()", ("GetMailStatistics", "GetMailCount", "GetSpamScores"),
|
||||
"Mail statistics, hourly mail counts and spam-score distribution"),
|
||||
("MonitorQueues", ("GetQueueStatus",), "Each collected cluster node's queue status"),
|
||||
("MonitorQuarantine", ("GetQuarantineStatus",), "Both spam and virus quarantine counts"),
|
||||
("MonitorDomainStats", ("ListRelayDomains", "GetDomainStatistics"),
|
||||
"Relay-domain inventory and domain statistics"),
|
||||
):
|
||||
body = go_block(poll, f"if instanceCfg.{condition} {{")
|
||||
for method in methods:
|
||||
self.assertIn(f"client.{method}(", body)
|
||||
self.assertEqual(body.count(f"client.{method}("), poll.count(f"client.{method}("))
|
||||
self.assertIn(description, self.guide)
|
||||
for phrase in ("saved switches control these subsequent ordinary poll requests",
|
||||
"Edits apply to the next poll", "not requests already in flight",
|
||||
"does not dismiss an existing alert or establish recovery"):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_legacy_scope_and_explicit_false_readback_are_not_overpromised(self):
|
||||
cfg = (ROOT / "internal/config/config.go").read_text()
|
||||
default = go_block(cfg, "func (i PMGInstance) MailStatsEnabled() bool {")
|
||||
self.assertIn("!i.MonitoringConfigured", default)
|
||||
for flag in ("MonitorQueues", "MonitorQuarantine", "MonitorDomainStats"):
|
||||
self.assertIn(f"!i.{flag}", default)
|
||||
wire = (ROOT / "internal/api/configapi/config_handlers.go").read_text()
|
||||
response = go_block(wire, "type NodeResponse struct {")
|
||||
for flag in ("monitorMailStats", "monitorQueues", "monitorQuarantine", "monitorDomainStats"):
|
||||
self.assertIn(f'json:"{flag}"', response)
|
||||
self.assertNotIn(f'json:"{flag},omitempty"', response)
|
||||
for phrase in ("Saving all four options off preserves that choice when settings are reopened",
|
||||
"keeps the legacy mail-statistics default",
|
||||
"until its collection scope is explicitly saved",
|
||||
"cannot distinguish an old opt-out from an unset default"):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_dataset_opt_out_is_not_a_connection_pause(self):
|
||||
poll = (ROOT / "internal/monitoring/monitor_pbs_pmg.go").read_text()
|
||||
poll = poll.split("func (m *Monitor) pollPMGInstance(", 1)[1]
|
||||
for condition in ("MailStatsEnabled()", "MonitorQueues", "MonitorQuarantine", "MonitorDomainStats"):
|
||||
body = go_block(poll, f"if instanceCfg.{condition} {{")
|
||||
for method in ("GetVersion", "GetClusterStatus", "ListBackups"):
|
||||
self.assertIn(f"client.{method}(", poll)
|
||||
self.assertNotIn(f"client.{method}(", body)
|
||||
for phrase in ("Version, cluster and configuration-backup reads are independent",
|
||||
"all four off does not mean no PMG requests"):
|
||||
self.assertIn(phrase, self.guide)
|
||||
self.assertIn("if instanceCfg.Disabled", poll)
|
||||
self.assertLess(poll.index("if instanceCfg.Disabled"), poll.index("client.GetVersion"))
|
||||
for phrase in ("pause the PMG connection", "does not cancel a request already in flight",
|
||||
"arrange independent coverage", "only the connection you deliberately paused"):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_published_warning_is_bounded_to_the_named_release(self):
|
||||
for phrase in ("Published v6.5.0 does not contain this scope correction",
|
||||
"In that version, mail, queue and quarantine requests can still run",
|
||||
"domain statistics alone follow their switch",
|
||||
"assume development-source behaviour is present in an installed release"):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_incomplete_collection_does_not_promise_zero_or_recovery(self):
|
||||
for phrase in ("records downstream read failures as incomplete collection",
|
||||
"retaining the successful version connection",
|
||||
"Missing readings do not become observed zero totals",
|
||||
"Partial queue data can still raise the highest configured severity",
|
||||
"cannot clear or downgrade an existing alert",
|
||||
"quarantine recovery requires both categories",
|
||||
"do not make every empty display a verified zero"):
|
||||
self.assertIn(phrase, self.guide)
|
||||
|
||||
def test_diagnostics_preserve_mail_privacy_and_separate_failure_classes(self):
|
||||
for phrase in ("refusal is not a password verdict", "Certificate validation error",
|
||||
"401/403", "specific failed request", "configured polling interval",
|
||||
|
||||
Reference in New Issue
Block a user