Preserve fresh rolling-window metric authority

Keep in-memory observations authoritative over duplicate persisted history, add direct provider and cache reset coverage, and register the rolling-window proof files with canonical governance.\n\nChange-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-08-27 21:22:12 +01:00
parent 3608854f7e
commit fec18f343f
7 changed files with 90 additions and 6 deletions
@@ -437,10 +437,13 @@ identity and durable history as charts. `internal/monitoring/metric_window_provi
resolves the unified resource metrics target, reads the fresh in-memory tail,
and falls back to the SQLite metrics store when that tail lacks the requested
coverage. Persistent fallbacks are briefly cached to bound restart-time query
load, merged by timestamp, and returned as observations only; alert policy owns
averaging, readiness, hysteresis, and lifecycle decisions. Missing target,
query failure, shallow history, or gapped history must remain unknown at the
alerts boundary rather than being replaced by a synthetic healthy value.
load, merged by timestamp, and returned as observations only. When the durable
series and fresh in-memory tail contain the same timestamp, the in-memory value
is authoritative so an older persisted or rolled-up value cannot replace the
latest observation. Alert policy owns averaging, readiness, hysteresis, and
lifecycle decisions. Missing target, query failure, shallow history, or gapped
history must remain unknown at the alerts boundary rather than being replaced
by a synthetic healthy value.
Monitoring ingest keeps mock mode hermetic. The unified read path already
substitutes the mock snapshot wholesale, so anything that runs after that
@@ -2334,7 +2334,8 @@
"internal/alerts/unified_eval_parity_test.go",
"internal/alerts/unified_eval_test.go",
"internal/alerts/unified_incident_confirmation_test.go",
"internal/alerts/unified_incidents_test.go"
"internal/alerts/unified_incidents_test.go",
"internal/alerts/windowed_metric_test.go"
]
},
{
@@ -2540,6 +2541,7 @@
"internal/alerts/callback_config_coverage_test.go",
"internal/alerts/canonical_override_migration_test.go",
"internal/alerts/canonical_stateful_test.go",
"internal/alerts/config/evaluation_windows_test.go",
"internal/alerts/config_validation_test.go",
"internal/alerts/eventlog/eventlog_snapshot_test.go",
"internal/alerts/external_probe_test.go",
@@ -2564,6 +2566,7 @@
"internal/alerts/threshold_resolution_shared_test.go",
"internal/alerts/unified_incident_confirmation_test.go",
"internal/alerts/update_alerts_test.go",
"internal/alerts/windowed_metric_test.go",
"internal/monitoring/deadman_test.go",
"internal/monitoring/monitor_alert_override_migration_test.go"
]
@@ -5715,6 +5718,7 @@
"allow_same_subsystem_tests": false,
"test_prefixes": [],
"exact_files": [
"internal/monitoring/metric_window_provider_test.go",
"internal/monitoring/metrics_history_concurrency_test.go",
"internal/monitoring/metrics_history_memory_regression_test.go",
"internal/monitoring/metrics_history_test.go",
@@ -6055,6 +6059,7 @@
"internal/monitoring/issue1595_collection_trust_test.go",
"internal/monitoring/issue1613_contract_test.go",
"internal/monitoring/issue1638_dns_cache_test.go",
"internal/monitoring/metric_window_provider_test.go",
"internal/monitoring/monitor_additional_test.go",
"internal/monitoring/monitor_alert_intent_test.go",
"internal/monitoring/monitor_alert_override_migration_test.go",
@@ -58,7 +58,9 @@ func (m *Monitor) metricWindowPoints(request alerts.MetricWindowRequest) ([]aler
points := m.inMemoryMetricWindow(resourceType, resourceID, metric, duration)
if metricWindowCoverage(points) < duration*8/10 {
stored := m.persistentMetricWindow(resourceType, resourceID, metric, request.Start, request.End)
points = mergeMetricWindowPoints(points, stored, request.Start, request.End)
// Append the fresh in-memory tail last so it remains authoritative when
// SQLite contains an older value at the same timestamp.
points = mergeMetricWindowPoints(stored, points, request.Start, request.End)
}
result := make([]alerts.MetricWindowPoint, 0, len(points))
@@ -0,0 +1,65 @@
package monitoring
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
)
func TestMetricWindowPointsUsesInMemoryMetricAlias(t *testing.T) {
now := time.Now().UTC()
history := NewMetricsHistory(32, time.Hour)
history.AddGuestMetric("vm-1", "netin", 12, now.Add(-4*time.Minute))
history.AddGuestMetric("vm-1", "netin", 18, now.Add(-2*time.Minute))
monitor := &Monitor{metricsHistory: history}
points, err := monitor.metricWindowPoints(alerts.MetricWindowRequest{
ResourceID: "vm-1",
ResourceType: "vm",
Metric: "networkIn",
Start: now.Add(-5 * time.Minute),
End: now,
})
if err != nil {
t.Fatalf("metricWindowPoints returned error: %v", err)
}
if len(points) != 2 || points[0].Value != 12 || points[1].Value != 18 {
t.Fatalf("metricWindowPoints = %+v, want canonical netin history", points)
}
}
func TestMetricWindowMergePrefersFreshInMemoryDuplicate(t *testing.T) {
now := time.Now().UTC()
stored := []MetricPoint{
{Timestamp: now.Add(-4 * time.Minute), Value: 40},
{Timestamp: now.Add(-2 * time.Minute), Value: 99},
}
inMemory := []MetricPoint{
{Timestamp: now.Add(-2 * time.Minute), Value: 55},
{Timestamp: now.Add(-time.Minute), Value: 60},
}
got := mergeMetricWindowPoints(stored, inMemory, now.Add(-5*time.Minute), now)
if len(got) != 3 {
t.Fatalf("mergeMetricWindowPoints returned %d points, want 3: %+v", len(got), got)
}
if got[1].Value != 55 {
t.Fatalf("duplicate timestamp value = %.1f, want fresh in-memory value 55", got[1].Value)
}
}
func TestMetricsHistoryResetClearsMetricWindowCache(t *testing.T) {
history := NewMetricsHistory(32, time.Hour)
history.metricWindowCache = map[string]metricWindowCacheEntry{
"vm\x00vm-1\x00cpu\x00300": {
points: []alerts.MetricWindowPoint{{Timestamp: time.Now().UTC(), Value: 42}},
expiresAt: time.Now().Add(time.Minute),
},
}
history.Reset()
if history.metricWindowCache != nil {
t.Fatalf("metric window cache survived reset: %+v", history.metricWindowCache)
}
}
@@ -304,6 +304,7 @@ class CanonicalCompletionGuardTest(unittest.TestCase):
"internal/monitoring/issue1595_collection_trust_test.go",
"internal/monitoring/issue1613_contract_test.go",
"internal/monitoring/issue1638_dns_cache_test.go",
"internal/monitoring/metric_window_provider_test.go",
"internal/monitoring/monitor_additional_test.go",
"internal/monitoring/monitor_alert_intent_test.go",
"internal/monitoring/monitor_alert_override_migration_test.go",
@@ -87,10 +87,13 @@ class RegistryAuditTest(unittest.TestCase):
},
("alerts", "canonical-alert-runtime"): {
"internal/alerts/intent_policy_test.go",
"internal/alerts/windowed_metric_test.go",
},
("alerts", "alerts-runtime-support"): {
"internal/alerts/canonical_override_migration_test.go",
"internal/alerts/config/evaluation_windows_test.go",
"internal/alerts/intent_policy_test.go",
"internal/alerts/windowed_metric_test.go",
"internal/monitoring/monitor_alert_override_migration_test.go",
},
("api-contracts", "alert-intent-policy-api"): {
@@ -126,9 +129,13 @@ class RegistryAuditTest(unittest.TestCase):
("monitoring", "monitoring-runtime"): {
"internal/monitoring/availability_udp_test.go",
"internal/monitoring/issue1595_collection_trust_test.go",
"internal/monitoring/metric_window_provider_test.go",
"internal/monitoring/monitor_alert_intent_test.go",
"internal/monitoring/monitor_alert_override_migration_test.go",
},
("monitoring", "metrics-history-runtime"): {
"internal/monitoring/metric_window_provider_test.go",
},
("monitoring", "agent-fleet-diagnostics-runtime"): {
"internal/api/agent_fleet_doctor_test.go",
"internal/api/connections_aggregator_test.go",
@@ -4230,6 +4230,7 @@ class SubsystemLookupTest(unittest.TestCase):
self.assertEqual(
match["verification_requirement"]["exact_files"],
[
"internal/monitoring/metric_window_provider_test.go",
"internal/monitoring/metrics_history_concurrency_test.go",
"internal/monitoring/metrics_history_memory_regression_test.go",
"internal/monitoring/metrics_history_test.go",