Complete schema 14 privacy disclosure

Name every outbound alert-quality field in both operator-facing privacy documents, lock the current schema value in verification, and align the alert-manager adoption test with the telemetry contract.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-08-29 15:23:28 +01:00
parent 523bb3f705
commit f29e7397b1
5 changed files with 112 additions and 76 deletions
+22 -9
View File
@@ -37,7 +37,7 @@ Every field is listed below with the reason it exists. Nothing else is included
| Field | Example | Purpose |
|-------|---------|---------|
| Schema version | `13` | Identify the exact payload contract so old and new signals are not mixed silently |
| Schema version | `14` | Identify the exact payload contract so old and new signals are not mixed silently |
| Sent at | `2026-07-23T08:30:00Z` | Date the individual heartbeat without sending a history of client activity |
| Install ID | `a1b2c3d4-...` | Distinguish active installations within one rotation window without tying telemetry to an account or person |
| Version | `6.0.0-rc.1` | Track the canonical release identity currently deployed |
@@ -93,17 +93,30 @@ Every field is listed below with the reason it exists. Nothing else is included
| Alerts fired 30d | `18` | Count unique locally retained alert occurrences in the current 30-day window without sending alert text, resource IDs, or timestamps |
| Alerts acknowledged 30d | `7` | Count acknowledgements in the current 30-day window without sending actors, reasons, alert IDs, or timestamps |
| Alerts resolved 30d | `12` | Count resolved alert records in the current 30-day window without sending resolution details, alert IDs, or resource IDs |
| Active alerts by severity | `1` info, `2` warning, `1` critical | Three aggregate counts that reconcile to active alerts |
| Active alert age buckets | `1` under 1h, `2` 1h-24h, `1` 1d-7d, `0` 7d+ | Four closed age buckets with no alert timestamps |
| Alerts fired by severity 30d | `2` info, `10` warning, `6` critical | Three aggregate occurrence counts that reconcile to alerts fired 30d |
| Alerts resolved by severity 30d | `1` info, `7` warning, `4` critical | Three aggregate occurrence counts that reconcile to alerts resolved 30d |
| Alert resolution duration buckets 30d | `3` under 15m, `2` 15m-1h, `5` 1h-24h, `2` 1d-7d, `0` 7d+ | Five closed time-to-resolution buckets with no occurrence timestamps |
| Repeat alert occurrences 30d | `4` | Count occurrences after the first local occurrence of the same canonical alert identity, without sending that identity |
| Snoozed alert occurrences 30d | `3` | Count occurrences with a durable suppression lifecycle transition |
| Active alerts info | `1` | Count currently active info alerts without sending alert content or identity |
| Active alerts warning | `2` | Count currently active warning alerts without sending alert content or identity |
| Active alerts critical | `1` | Count currently active critical alerts without sending alert content or identity |
| Active alerts age under 1h | `1` | Count active alerts in the closed under-one-hour age bucket without sending timestamps |
| Active alerts age 1h 24h | `2` | Count active alerts in the closed one-hour-to-one-day age bucket without sending timestamps |
| Active alerts age 1d 7d | `1` | Count active alerts in the closed one-day-to-seven-day age bucket without sending timestamps |
| Active alerts age 7d plus | `0` | Count active alerts in the closed seven-days-or-more age bucket without sending timestamps |
| Alerts fired info 30d | `2` | Count info alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts fired warning 30d | `10` | Count warning alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts fired critical 30d | `6` | Count critical alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolved info 30d | `1` | Count resolved info alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolved warning 30d | `7` | Count resolved warning alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolved critical 30d | `4` | Count resolved critical alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolution under 15m 30d | `3` | Count resolved occurrences in the closed under-15-minute duration bucket without sending timestamps |
| Alerts resolution 15m 1h 30d | `2` | Count resolved occurrences in the closed 15-minute-to-one-hour duration bucket without sending timestamps |
| Alerts resolution 1h 24h 30d | `5` | Count resolved occurrences in the closed one-hour-to-one-day duration bucket without sending timestamps |
| Alerts resolution 1d 7d 30d | `2` | Count resolved occurrences in the closed one-day-to-seven-day duration bucket without sending timestamps |
| Alerts resolution 7d plus 30d | `0` | Count resolved occurrences in the closed seven-days-or-more duration bucket without sending timestamps |
| Alerts repeat occurrences 30d | `4` | Count occurrences after the first local occurrence of the same canonical alert identity, without sending that identity |
| Alerts snoozed occurrences 30d | `3` | Count occurrences with a durable suppression lifecycle transition |
| Alerts resolved while snoozed 30d | `2` | Count snoozed occurrences whose recovery transition arrived before an unsnooze transition |
| Alert manager tenants | `2` | Denominator for tenant-level alert configuration and persistence fields |
| Alert delivery active tenants | `2` | Count tenant alert managers with delivery enabled and activation state active |
| Alert flapping detection enabled tenants | `2` | Count tenant alert managers with flapping detection configured, not detected flapping episodes |
| Alert flapping enabled tenants | `2` | Count tenant alert managers with flapping detection configured, not detected flapping episodes |
| Alert intent policy configured tenants | `1` | Count tenant alert managers with an explicitly revised default, resource-type, or resource intent policy |
| Alert event history authoritative tenants | `2` | Count tenant alert managers whose durable event history is authoritative |
| Alert active state authoritative tenants | `2` | Count tenant alert managers whose durable active-state projection is authoritative |
@@ -74,6 +74,10 @@ than accepting secret values.
The canonical and shipped privacy documents must state this retained-data and
telemetry boundary explicitly and remain byte-for-byte synchronized, so the
promise visible inside the product cannot drift from the repository policy.
Their telemetry table must identify the current schema version and name every
outbound JSON field individually. Grouped prose may explain related aggregates,
but it cannot replace field-level disclosure or make a newly added counter
invisible to operators reviewing exactly what Pulse sends.
## Canonical Files
+22 -9
View File
@@ -37,7 +37,7 @@ Every field is listed below with the reason it exists. Nothing else is included
| Field | Example | Purpose |
|-------|---------|---------|
| Schema version | `13` | Identify the exact payload contract so old and new signals are not mixed silently |
| Schema version | `14` | Identify the exact payload contract so old and new signals are not mixed silently |
| Sent at | `2026-07-23T08:30:00Z` | Date the individual heartbeat without sending a history of client activity |
| Install ID | `a1b2c3d4-...` | Distinguish active installations within one rotation window without tying telemetry to an account or person |
| Version | `6.0.0-rc.1` | Track the canonical release identity currently deployed |
@@ -93,17 +93,30 @@ Every field is listed below with the reason it exists. Nothing else is included
| Alerts fired 30d | `18` | Count unique locally retained alert occurrences in the current 30-day window without sending alert text, resource IDs, or timestamps |
| Alerts acknowledged 30d | `7` | Count acknowledgements in the current 30-day window without sending actors, reasons, alert IDs, or timestamps |
| Alerts resolved 30d | `12` | Count resolved alert records in the current 30-day window without sending resolution details, alert IDs, or resource IDs |
| Active alerts by severity | `1` info, `2` warning, `1` critical | Three aggregate counts that reconcile to active alerts |
| Active alert age buckets | `1` under 1h, `2` 1h-24h, `1` 1d-7d, `0` 7d+ | Four closed age buckets with no alert timestamps |
| Alerts fired by severity 30d | `2` info, `10` warning, `6` critical | Three aggregate occurrence counts that reconcile to alerts fired 30d |
| Alerts resolved by severity 30d | `1` info, `7` warning, `4` critical | Three aggregate occurrence counts that reconcile to alerts resolved 30d |
| Alert resolution duration buckets 30d | `3` under 15m, `2` 15m-1h, `5` 1h-24h, `2` 1d-7d, `0` 7d+ | Five closed time-to-resolution buckets with no occurrence timestamps |
| Repeat alert occurrences 30d | `4` | Count occurrences after the first local occurrence of the same canonical alert identity, without sending that identity |
| Snoozed alert occurrences 30d | `3` | Count occurrences with a durable suppression lifecycle transition |
| Active alerts info | `1` | Count currently active info alerts without sending alert content or identity |
| Active alerts warning | `2` | Count currently active warning alerts without sending alert content or identity |
| Active alerts critical | `1` | Count currently active critical alerts without sending alert content or identity |
| Active alerts age under 1h | `1` | Count active alerts in the closed under-one-hour age bucket without sending timestamps |
| Active alerts age 1h 24h | `2` | Count active alerts in the closed one-hour-to-one-day age bucket without sending timestamps |
| Active alerts age 1d 7d | `1` | Count active alerts in the closed one-day-to-seven-day age bucket without sending timestamps |
| Active alerts age 7d plus | `0` | Count active alerts in the closed seven-days-or-more age bucket without sending timestamps |
| Alerts fired info 30d | `2` | Count info alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts fired warning 30d | `10` | Count warning alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts fired critical 30d | `6` | Count critical alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolved info 30d | `1` | Count resolved info alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolved warning 30d | `7` | Count resolved warning alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolved critical 30d | `4` | Count resolved critical alert occurrences in the current 30-day window without sending alert content or identity |
| Alerts resolution under 15m 30d | `3` | Count resolved occurrences in the closed under-15-minute duration bucket without sending timestamps |
| Alerts resolution 15m 1h 30d | `2` | Count resolved occurrences in the closed 15-minute-to-one-hour duration bucket without sending timestamps |
| Alerts resolution 1h 24h 30d | `5` | Count resolved occurrences in the closed one-hour-to-one-day duration bucket without sending timestamps |
| Alerts resolution 1d 7d 30d | `2` | Count resolved occurrences in the closed one-day-to-seven-day duration bucket without sending timestamps |
| Alerts resolution 7d plus 30d | `0` | Count resolved occurrences in the closed seven-days-or-more duration bucket without sending timestamps |
| Alerts repeat occurrences 30d | `4` | Count occurrences after the first local occurrence of the same canonical alert identity, without sending that identity |
| Alerts snoozed occurrences 30d | `3` | Count occurrences with a durable suppression lifecycle transition |
| Alerts resolved while snoozed 30d | `2` | Count snoozed occurrences whose recovery transition arrived before an unsnooze transition |
| Alert manager tenants | `2` | Denominator for tenant-level alert configuration and persistence fields |
| Alert delivery active tenants | `2` | Count tenant alert managers with delivery enabled and activation state active |
| Alert flapping detection enabled tenants | `2` | Count tenant alert managers with flapping detection configured, not detected flapping episodes |
| Alert flapping enabled tenants | `2` | Count tenant alert managers with flapping detection configured, not detected flapping episodes |
| Alert intent policy configured tenants | `1` | Count tenant alert managers with an explicitly revised default, resource-type, or resource intent policy |
| Alert event history authoritative tenants | `2` | Count tenant alert managers whose durable event history is authoritative |
| Alert active state authoritative tenants | `2` | Count tenant alert managers whose durable active-state projection is authoritative |
@@ -157,13 +157,11 @@ func TestBranchcov0723Am_AccumulateInstallOutcomeCounts(t *testing.T) {
assert.Equal(t, sentryCounts(), counts)
})
t.Run("alert_manager_with_empty_history_increments_nothing", func(t *testing.T) {
t.Run("alert_manager_with_empty_history_reports_configuration_adoption", func(t *testing.T) {
// Covers the alertManager != nil true-arm. A freshly-constructed alert
// manager has no history, so GetAlertHistorySince returns nothing and
// no alert-outcome counts accrue. (Deeper history-driven counting is
// exercised directly on accumulateAlertOutcomeCounts above; history
// cannot be injected into an alerts.Manager from outside the alerts
// package without driving the full evaluation pipeline.)
// manager has no history, so no lifecycle outcome counts accrue. It does
// still contribute one manager and its default-enabled flapping posture;
// those are configuration adoption denominators rather than outcomes.
alertMgr := alerts.NewManagerWithDataDir(t.TempDir())
defer alertMgr.Stop()
@@ -172,7 +170,10 @@ func TestBranchcov0723Am_AccumulateInstallOutcomeCounts(t *testing.T) {
counts := sentryCounts()
accumulateInstallOutcomeCounts(&counts, mon, now)
assert.Equal(t, sentryCounts(), counts, "empty history must not change any count")
expected := sentryCounts()
expected.AlertQuality.ManagerTenants = 1
expected.AlertQuality.FlappingEnabledTenants = 1
assert.Equal(t, expected, counts, "empty history must only add configuration adoption")
})
}
+56 -51
View File
@@ -2,6 +2,7 @@ package telemetry
import (
"encoding/json"
"fmt"
"io"
"io/fs"
"net/http"
@@ -335,57 +336,57 @@ func TestApplySnapshot(t *testing.T) {
snap := func() Snapshot {
return Snapshot{
PVENodes: 3,
VMs: 10,
Containers: 5,
AgentHosts: 2,
DockerContainers: 12,
KubernetesPods: 18,
StoragePools: 4,
PhysicalDisks: 9,
TrueNASSystems: 1,
TrueNASApps: 3,
VMwareHosts: 2,
AvailabilityTargets: 6,
AIEnabled: true,
PatrolEnabled: true,
DiscoveryEnabled: true,
NotificationsEnabled: true,
AIActionsEnabled: true,
AlertAIEnabled: true,
ActiveAlerts: 2,
PaidLicense: true,
HasAPITokens: true,
RBACCustomRoles: 3,
RBACUserAssignments: 7,
AuditReads30d: 41,
ReportSchedules: 5,
ReportSchedulesEnabled: 4,
ReportSchedulesRun30d: 2,
AgentProfiles: 9,
UpdateAttempts30d: 4,
UpdateSuccesses30d: 2,
UpdateFailures30d: 1,
UpdateLastFailureCategory: "download",
ActiveAlertsCritical: 2,
ActiveAlertsAge1h24h: 2,
AlertsFiredCritical30d: 5,
AlertsResolvedCritical30d: 3,
AlertsResolution1h24h30d: 3,
AlertsRepeatOccurrences30d: 2,
AlertsSnoozedOccurrences30d: 2,
AlertsResolvedWhileSnoozed30d: 1,
AlertManagerTenants: 2,
AlertDeliveryActiveTenants: 1,
AlertFlappingEnabledTenants: 2,
AlertIntentPolicyConfiguredTenants: 1,
AlertEventHistoryAuthoritativeTenants: 2,
AlertActiveStateAuthoritativeTenants: 2,
PulseIntelligenceLoopConfigured: true,
PulseIntelligenceLoopActive30d: true,
PulseIntelligenceCompleteOperationsLoop30d: true,
PulseIntelligenceApprovedExecutionLoop30d: true,
PulseIntelligenceResolvedOperationsLoop30d: true,
PVENodes: 3,
VMs: 10,
Containers: 5,
AgentHosts: 2,
DockerContainers: 12,
KubernetesPods: 18,
StoragePools: 4,
PhysicalDisks: 9,
TrueNASSystems: 1,
TrueNASApps: 3,
VMwareHosts: 2,
AvailabilityTargets: 6,
AIEnabled: true,
PatrolEnabled: true,
DiscoveryEnabled: true,
NotificationsEnabled: true,
AIActionsEnabled: true,
AlertAIEnabled: true,
ActiveAlerts: 2,
PaidLicense: true,
HasAPITokens: true,
RBACCustomRoles: 3,
RBACUserAssignments: 7,
AuditReads30d: 41,
ReportSchedules: 5,
ReportSchedulesEnabled: 4,
ReportSchedulesRun30d: 2,
AgentProfiles: 9,
UpdateAttempts30d: 4,
UpdateSuccesses30d: 2,
UpdateFailures30d: 1,
UpdateLastFailureCategory: "download",
ActiveAlertsCritical: 2,
ActiveAlertsAge1h24h: 2,
AlertsFiredCritical30d: 5,
AlertsResolvedCritical30d: 3,
AlertsResolution1h24h30d: 3,
AlertsRepeatOccurrences30d: 2,
AlertsSnoozedOccurrences30d: 2,
AlertsResolvedWhileSnoozed30d: 1,
AlertManagerTenants: 2,
AlertDeliveryActiveTenants: 1,
AlertFlappingEnabledTenants: 2,
AlertIntentPolicyConfiguredTenants: 1,
AlertEventHistoryAuthoritativeTenants: 2,
AlertActiveStateAuthoritativeTenants: 2,
PulseIntelligenceLoopConfigured: true,
PulseIntelligenceLoopActive30d: true,
PulseIntelligenceCompleteOperationsLoop30d: true,
PulseIntelligenceApprovedExecutionLoop30d: true,
PulseIntelligenceResolvedOperationsLoop30d: true,
PulseIntelligencePatrolControlCompletedOperationsLoop30d: true,
PulseIntelligencePatrolControlResolvedOperationsLoop30d: true,
PulseIntelligencePatrolControlPaidCompletedOperationsLoop30d: true,
@@ -697,6 +698,10 @@ func TestAllTelemetryFieldsAreDisclosed(t *testing.T) {
t.Fatalf("read %s: %v", relativePath, err)
}
doc := normalizedTelemetryDisclosureTableText(string(raw))
schemaDisclosure := fmt.Sprintf("schema version %d", TelemetrySchemaVersion)
if !strings.Contains(doc, schemaDisclosure) {
t.Errorf("%s must identify the current telemetry contract as %q", relativePath, schemaDisclosure)
}
for _, label := range fieldLabels {
if !strings.Contains(doc, label) {
t.Errorf("%s must disclose telemetry field %q", relativePath, label)