Repository navigation
Expand file tree
/
Copy pathtelemetry.go
More file actions
1946 lines (1829 loc) · 106 KB
/
Copy pathtelemetry.go
File metadata and controls
1946 lines (1829 loc) · 106 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Package telemetry provides outbound usage telemetry for Pulse.
//
// Pulse sends a lightweight ping on startup and once every 24 hours to help the
// developer understand how many active installations exist and which features are
// in use. Telemetry is enabled by default and can be opted out at any time.
//
// # What is sent (the full list — nothing else)
//
// Contract and identity:
// - Payload schema version and the UTC time this payload was built
// - A rotating install ID (UUID, generated locally and rotated periodically, not tied to any account)
// - Pulse version identity (normalized version plus raw build string when it differs)
// - Platform: "docker" or "binary"
// - Coarse deployment method from a fixed list, never an image name or path
// - OS and architecture (e.g. "linux/amd64")
//
// Lifecycle and audience posture (closed buckets, booleans, and counts only):
// - Known install age, highest activation stage, time to first monitored resource, and estate-size buckets
// - Whether authentication is configured, number of configured connections, and whether monitoring is active
// - Whether a core outcome was observed in the current aggregate windows
// - Whether the local API, UI document, and referenced frontend assets are
// being served, plus the immediately previous release observation
//
// Scale (counts only, no names):
// - Number of PVE nodes, PBS instances, PMG instances
// - Number of VMs, LXC containers
// - Number of Pulse Agent hosts, Docker hosts/containers, and Kubernetes clusters/nodes/pods/deployments
// - Number of storage resources, physical disks, Ceph clusters, and network shares
// - Number of TrueNAS systems/VMs/apps, VMware hosts/VMs/datastores, and availability targets
// - Number of availability targets assigned to external probes, and of distinct probe agents
//
// Feature usage (booleans and counts, no content):
// - Whether AI features are enabled
// - Whether Patrol, discovery, notifications, or AI action capability are enabled
// - Number of active alerts
// - Whether relay/remote access is enabled
// - Whether SSO/OIDC is configured
// - Whether multi-tenant mode is enabled
// - Whether a paid license is active
// - Whether any API tokens are configured
// - Aggregate alert fired/acknowledged/resolved counts over 30 days
// - Aggregate alert lifecycle counts by severity, active-alert age and
// resolution-duration buckets, repeat and snooze outcomes over 30 days
// - Tenant counts for alert delivery, flapping and intent-policy adoption,
// plus event-history and active-state persistence health
// - Aggregate notification attempt/delivery/failure counts over seven days
// - Coarse update funnel counters and last failure category over the current install-ID rotation window
// - Workload-history preview, scrub, range-change, and Details-selection
// session counts over the current install-ID rotation window
// - Patrol, Assistant, and external-agent usage counters over the current install-ID rotation window:
// configured/active/governed-action/approved-execution/resolved-loop state,
// Patrol control completed-loop and resolved-loop proof reported through
// legacy pro_activation metric keys for cohort continuity,
// operations-loop workflow starter request counts by surface, Assistant/Patrol AI calls,
// Patrol runs/new findings/investigations/resolved findings/autofixes,
// the fixed machine cause code when an enabled Patrol is blocked from running,
// external-agent readiness/usage, action plans, approval requests, rejected
// action decisions, approved action decisions, approved action attempts,
// and approved action successes
//
// # What is NOT sent
//
// - No IP addresses are included in the payload or stored in telemetry rows
// - No hostnames, node names, VM names, or any infrastructure identifiers
// - No Proxmox credentials, API tokens, or passwords
// - No alert content, AI prompts, chat messages, command text, action output, or token values
// - No action targets, resource IDs, finding IDs, approval actors, or approval reasons
// - No names, email addresses, account identifiers, or other intentionally identifying personal content
// - No listener addresses, self-check URLs, response bodies, asset names, or raw startup/self-check errors
// - No raw browser events, clickstream, guest/user identity, routes, selected
// ranges, cursor values, interaction timing, or browser identity
//
// # How to disable
//
// Set the environment variable PULSE_TELEMETRY=false, or toggle off
// "Outbound usage telemetry" in Settings → System → General.
//
// # Mock mode
//
// While mock/demo fixture mode is enabled, outbound pings are suppressed
// entirely: a mock-mode boot (e2e, CI, qual runs, demo containers) would
// otherwise report the synthetic fixture fleet as a real installation.
//
// # Test binaries
//
// Pings to the production endpoint are suppressed inside a Go test binary for
// the same reason: a test that boots the real server is not an installation,
// and its throwaway data directory mints a new install ID on every run. Tests
// that need to assert on ping content redirect pingEndpoint to a local server,
// which the guard deliberately allows.
package telemetry
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
"time"
"github.com/google/uuid"
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/rcourtman/pulse-go-rewrite/internal/updates"
"github.com/rs/zerolog/log"
)
// productionPingEndpoint is the live receiver for outbound usage telemetry.
const productionPingEndpoint = "https://license.pulserelay.pro/v1/telemetry/ping"
// pingEndpoint is the URL that receives outbound usage telemetry pings.
// It is a var (not const) so that tests can redirect it to a local server.
var pingEndpoint = productionPingEndpoint
var errInstallIDUnavailable = errors.New("telemetry install id unavailable")
// errProductionEndpointUnderTest reports a ping suppressed because a test
// binary tried to reach the live receiver.
var errProductionEndpointUnderTest = errors.New("telemetry: refusing to post to the production endpoint from a test binary")
const (
// heartbeatInterval is the base interval between daily pings.
// Each cycle adds random jitter of ±maxHeartbeatJitter to prevent
// thundering-herd effects when many installations start simultaneously.
heartbeatInterval = 24 * time.Hour
// maxHeartbeatJitter is the maximum random offset added to each heartbeat.
maxHeartbeatJitter = 30 * time.Minute
// startupDelay is how long to wait after startup before sending the first
// ping, giving the monitor time to connect to nodes and populate state.
startupDelay = 2 * time.Minute
// httpTimeout is the maximum time for a single telemetry request.
httpTimeout = 10 * time.Second
// installIDFile is the filename persisted in the data directory.
installIDFile = ".install_id"
// lifecycleStateFile stores only local milestone timestamps and the highest
// coarse activation stage observed. It contains no user or infrastructure
// identifiers and is intentionally independent from the rotating install ID.
lifecycleStateFile = ".telemetry_lifecycle"
// serviceHealthStateFile retains only the current and immediately previous
// release observation. It lets a post-upgrade ping describe a before/after
// cohort without exporting listener addresses, URLs, errors, or request data.
serviceHealthStateFile = ".telemetry_service_health"
// installIDRotationWindow limits how long the same pseudonymous identifier
// can be reused before it is rotated locally.
installIDRotationWindow = 30 * 24 * time.Hour
// PulseIntelligenceTelemetryWindow is the rolling, content-free usage
// window used for Patrol, Assistant, and external-agent usage counters. It
// intentionally matches the install-ID rotation window so counters cannot
// be linked to one stable pseudonymous identifier indefinitely.
PulseIntelligenceTelemetryWindow = installIDRotationWindow
// TelemetrySchemaVersion identifies the exact outbound payload contract.
// Schema v3 makes notification_failures_7d a terminal-delivery count;
// schema v4 adds complete approved-action outcome accounting, fixed
// pre-dispatch refusal categories, and verified finding-resolution linkage.
// Schema v5 adds bounded, content-free notification failure classes.
// Schema v6 adds licensed-feature adoption counts (RBAC, audit logging,
// scheduled reporting, agent profiles, alert-triggered AI) and drops
// pulse_intelligence_patrol_autofixes_30d, which no code path could ever
// set to a non-zero value.
// Schema v7 replaces audit_logging_persistent and audit_events_30d with
// audit_reads_30d. The SQLite audit logger is installed on every install
// for defense in depth, so the boolean was true everywhere and the event
// count measured background write volume; neither discriminated an install
// that uses audit logging from one that merely has it. Reaching a
// license-gated audit read does.
// Schema v8 splits agent-side pre-mutation refusals into target-change,
// prerequisite, and invalid-contract categories so the legacy "other"
// bucket no longer hides actionable product failures.
// Schema v9 separates refusals that carried no machine reason code at all
// from refusals whose code is simply unrecognised. Agents that predate the
// typed refusal contract report the former, and folding them into "other"
// made a starved split indistinguishable from a broken one.
// Schema v10 adds the Patrol runtime blocked cause so an enabled Patrol
// that cannot run at all (no provider, unsuitable model, open circuit)
// is distinguishable in the fleet from one that runs and finds nothing.
// Field telemetry showed both present identically: high run counts with
// zero AI calls and zero findings.
// Schema v11 adds node connection test attempt and failure counts. The
// fleet shows a population that configures authentication and then never
// saves a connection, concentrated three to one in container deployments,
// and nothing recorded whether those installs tried to reach a node and
// failed or never attempted one at all.
// Schema v12 adds Patrol-origin action funnel counters so detection and
// investigation activity can be separated from proposal, decision,
// execution, and successful completion without exporting action identity.
// Schema v13 adds a local UI/API service observation and the immediately
// previous release observation. This separates a process that can emit
// telemetry from one that is actually serving its API and frontend assets,
// while retaining only fixed categories and release identity.
// Schema v14 adds identity-free alert quality outcomes and their tenant
// denominators. It uses closed severity, age, and resolution-time buckets,
// and reports only aggregate lifecycle, adoption, and persistence health.
// Schema v15 separates destination HTTP 5xx server failures from rejected
// HTTP 4xx responses. Both remain aggregate terminal-failure counters.
// Schema v16 adds four content-free workload-history adoption counters.
// Each browser reports each closed milestone at most once per session to a
// bounded local daily tally; no event stream or browser identity is sent.
// Schema v17 adds the closed Patrol provider class (local, cloud_byok,
// cloud_subscription, hosted_legacy, none), the effective Patrol
// autonomy level, coarse 30-day Patrol input and output token buckets, and
// per-outcome investigation counts. The 2026-09-01 Patrol assessment could
// not tell local from cloud installs, estimate what Patrol costs an
// install, or see how the investigations that produced no plan ended. No
// provider ID, model name, endpoint, account identity, exact token count,
// finding ID, or resource ID is exported.
// Schema v18 adds update_channel (stable or rc), update_check_outcome (a
// closed category for the last check on that channel) and update_available.
// A 6.4.3-rc.1 update check failed on every call for weeks while its
// telemetry looked healthy, because the update counters only see applied
// updates. No version, URL, or error text is exported.
// Schema v19 adds only the rolling count of successfully served metric
// history responses containing data older than seven days.
// Schema v20 adds aggregate reporting outcomes and distinct-period reuse.
// Schema v21 adds only the count of distinct days, 0 to 30, on which an
// external agent successfully read change or metric history.
TelemetrySchemaVersion = 21
)
type installIDRecord struct {
InstallID string `json:"install_id"`
IssuedAt time.Time `json:"issued_at"`
}
type lifecycleRecord struct {
FirstObservedAt time.Time `json:"first_observed_at"`
FirstMonitoredResourceAt *time.Time `json:"first_monitored_resource_at,omitempty"`
HighestObservedActivation string `json:"highest_observed_activation"`
}
// ServiceHealthObservation is the bounded result of Pulse's local UI/API
// self-check. FailureCategory must be one of the fixed ServiceHealthFailure*
// values. Callers must never put an address, URL, error string, or response
// content into this shape.
type ServiceHealthObservation struct {
Observed bool
Healthy bool
FailureCategory string
}
const (
ServiceHealthFailureListener = "listener"
ServiceHealthFailureStartup = "startup"
ServiceHealthFailureRuntime = "runtime"
ServiceHealthFailureAPIConnectivity = "api_connectivity"
ServiceHealthFailureTimeout = "timeout"
ServiceHealthFailureAPIStatus = "api_status"
ServiceHealthFailureUIStatus = "ui_status"
ServiceHealthFailureFrontendAssets = "frontend_assets"
ServiceHealthFailureUnknown = "unknown"
ServiceHealthCohortFirstObservation = "first_observation"
ServiceHealthCohortSameVersion = "same_version"
ServiceHealthCohortVersionChange = "version_change"
)
// Ping is the payload sent to the telemetry endpoint.
// Every field is documented here so users can audit exactly what leaves their server.
type Ping struct {
// Identity
SchemaVersion int `json:"schema_version"` // Versioned payload contract
SentAt string `json:"sent_at"` // UTC send/preview time; no client clock history
InstallID string `json:"install_id"` // Rotating UUID, not tied to any account
Version string `json:"version"` // Normalized Pulse version (e.g. "6.0.0-rc.1")
VersionRaw string `json:"version_raw,omitempty"` // Original version/build string when it differs
VersionChannel string `json:"version_channel"` // "stable", "rc", "dev", or "prerelease"
VersionBuild string `json:"version_build,omitempty"` // Build metadata when present (e.g. git describe suffix)
VersionDevelopment bool `json:"version_is_development"` // True for development/manual builds
VersionPublished bool `json:"version_is_published_release"` // True for published stable/RC asset versions
Platform string `json:"platform"` // "docker" or "binary"
OS string `json:"os"` // runtime.GOOS (e.g. "linux")
Arch string `json:"arch"` // runtime.GOARCH (e.g. "amd64")
Event string `json:"event"` // "startup" or "heartbeat"
DeploymentMethod string `json:"deployment_method"` // Closed coarse install method, never a path or image name
// Coarse lifecycle and audience posture. These are closed buckets and
// aggregate states only; no user, account, locale, or resource identity is
// included.
KnownInstallAgeBucket string `json:"known_install_age_bucket"`
ActivationStage string `json:"activation_stage"`
TimeToFirstMonitoredResourceBucket string `json:"time_to_first_monitored_resource_bucket"`
EstateSizeBucket string `json:"estate_size_bucket"`
AuthConfigured bool `json:"auth_configured"`
ConfiguredConnections int `json:"configured_connections"`
MonitoringActive bool `json:"monitoring_active"`
OutcomeObserved30d bool `json:"outcome_observed_30d"`
// Scale (counts only — no names, IPs, or identifiers)
PVENodes int `json:"pve_nodes"`
PBSInstances int `json:"pbs_instances"`
PMGInstances int `json:"pmg_instances"`
VMs int `json:"vms"`
Containers int `json:"containers"`
AgentHosts int `json:"agent_hosts"`
DockerHosts int `json:"docker_hosts"`
DockerContainers int `json:"docker_containers"`
KubernetesClusters int `json:"kubernetes_clusters"`
KubernetesNodes int `json:"kubernetes_nodes"`
KubernetesPods int `json:"kubernetes_pods"`
KubernetesDeployments int `json:"kubernetes_deployments"`
StoragePools int `json:"storage_pools"`
PhysicalDisks int `json:"physical_disks"`
CephClusters int `json:"ceph_clusters"`
NetworkShares int `json:"network_shares"`
TrueNASSystems int `json:"truenas_systems"`
TrueNASVMs int `json:"truenas_vms"`
TrueNASApps int `json:"truenas_apps"`
VMwareHosts int `json:"vmware_hosts"`
VMwareVMs int `json:"vmware_vms"`
VMwareDatastores int `json:"vmware_datastores"`
AvailabilityTargets int `json:"availability_targets"`
// Probe-assignment counts stay counts-only: no agent names, IDs, or
// addresses leave the install.
AvailabilityProbeTargets int `json:"availability_probe_targets"`
AvailabilityProbeAgents int `json:"availability_probe_agents"`
// Feature usage (booleans and counts — no content)
AIEnabled bool `json:"ai_enabled"`
PatrolEnabled bool `json:"patrol_enabled"`
DiscoveryEnabled bool `json:"discovery_enabled"`
NotificationsEnabled bool `json:"notifications_enabled"`
AIActionsEnabled bool `json:"ai_actions_enabled"`
AlertAIEnabled bool `json:"alert_ai_enabled"`
// AIProviderClass is the closed route class of the Patrol (or default)
// model: none, local, cloud_byok, cloud_subscription, hosted_legacy,
// or unknown. Never a provider ID, model name, endpoint, or account.
AIProviderClass string `json:"ai_provider_class"`
ActiveAlerts int `json:"active_alerts"`
RelayEnabled bool `json:"relay_enabled"`
SSOEnabled bool `json:"sso_enabled"`
MultiTenant bool `json:"multi_tenant"`
PaidLicense bool `json:"paid_license"`
HasAPITokens bool `json:"has_api_tokens"`
// Licensed-feature adoption. Counts only: no role names, schedule names,
// profile names, recipients, report contents, or audit event detail.
RBACCustomRoles int `json:"rbac_custom_roles"`
RBACUserAssignments int `json:"rbac_user_assignments"`
AuditReads30d int `json:"audit_reads_30d"`
ReportSchedules int `json:"report_schedules"`
ReportSchedulesEnabled int `json:"report_schedules_enabled"`
ReportSchedulesRun30d int `json:"report_schedules_run_30d"`
AgentProfiles int `json:"agent_profiles"`
UpdateAttempts30d int `json:"update_attempts_30d"`
UpdateSuccesses30d int `json:"update_successes_30d"`
UpdateFailures30d int `json:"update_failures_30d"`
// Last coarse update failure category; never raw error text.
UpdateLastFailureCategory string `json:"update_last_failure_category,omitempty"`
// Schema v18 update discovery: the channel the install follows and the
// closed outcome of its last check on that channel. A failed check never
// reaches the update counters above because nothing was applied.
UpdateChannel string `json:"update_channel"`
UpdateCheckOutcome string `json:"update_check_outcome"`
UpdateAvailable bool `json:"update_available"`
// Local release-service observation. The probe checks the local API, UI
// document, and referenced frontend assets. Only booleans, a fixed failure
// category, and normalized release versions leave the instance.
ServiceHealthObserved bool `json:"service_health_observed"`
ServiceHealthHealthy bool `json:"service_health_healthy"`
ServiceHealthFailureCategory string `json:"service_health_failure_category,omitempty"`
ServiceHealthCohort string `json:"service_health_cohort,omitempty"`
ServiceHealthPreviousVersion string `json:"service_health_previous_version,omitempty"`
ServiceHealthPreviousObserved bool `json:"service_health_previous_observed"`
ServiceHealthPreviousHealthy bool `json:"service_health_previous_healthy"`
// Node connection test outcomes over the install-ID rotation window.
// ConfiguredConnections counts only connections that were saved, so an
// install that tried to reach a node and could not is indistinguishable
// from one that never opened the add-node dialog: both report zero
// connections and stall at the "secured" activation stage. These separate
// the two. Counts only; hosts, credentials, and error text never leave the
// install. Successes are attempts minus failures.
NodeTestAttempts30d int `json:"node_test_attempts_30d"`
NodeTestFailures30d int `json:"node_test_failures_30d"`
// Workload history adoption over the install-ID rotation window. The local
// browser intake is session-deduplicated and contains only the closed
// milestone; guest/user identity, routes, coordinates, and timings never
// enter the tally or payload.
SuccessfulHistoryReadsOver7d30d int `json:"successful_history_reads_over_7d_30d"`
WorkloadHistoryPreviewSessions30d int `json:"workload_history_preview_sessions_30d"`
WorkloadHistoryScrubSessions30d int `json:"workload_history_scrub_sessions_30d"`
WorkloadHistoryRangeChangeSessions30d int `json:"workload_history_range_change_sessions_30d"`
WorkloadHistoryDetailsSelectionSessions30d int `json:"workload_history_details_selection_sessions_30d"`
// Core product outcomes. Alert history is retained locally for 30 days;
// notification delivery rows are locally retention-bounded to seven days.
AlertsFired30d int `json:"alerts_fired_30d"`
AlertsAcknowledged30d int `json:"alerts_acknowledged_30d"`
AlertsResolved30d int `json:"alerts_resolved_30d"`
// Alert quality aggregates. Severity totals reconcile to the existing
// lifecycle totals, while age and resolution-duration buckets have fixed
// inclusive lower and exclusive upper boundaries.
ActiveAlertsInfo int `json:"active_alerts_info"`
ActiveAlertsWarning int `json:"active_alerts_warning"`
ActiveAlertsCritical int `json:"active_alerts_critical"`
ActiveAlertsAgeUnder1h int `json:"active_alerts_age_under_1h"`
ActiveAlertsAge1h24h int `json:"active_alerts_age_1h_24h"`
ActiveAlertsAge1d7d int `json:"active_alerts_age_1d_7d"`
ActiveAlertsAge7dPlus int `json:"active_alerts_age_7d_plus"`
AlertsFiredInfo30d int `json:"alerts_fired_info_30d"`
AlertsFiredWarning30d int `json:"alerts_fired_warning_30d"`
AlertsFiredCritical30d int `json:"alerts_fired_critical_30d"`
AlertsResolvedInfo30d int `json:"alerts_resolved_info_30d"`
AlertsResolvedWarning30d int `json:"alerts_resolved_warning_30d"`
AlertsResolvedCritical30d int `json:"alerts_resolved_critical_30d"`
AlertsResolutionUnder15m30d int `json:"alerts_resolution_under_15m_30d"`
AlertsResolution15m1h30d int `json:"alerts_resolution_15m_1h_30d"`
AlertsResolution1h24h30d int `json:"alerts_resolution_1h_24h_30d"`
AlertsResolution1d7d30d int `json:"alerts_resolution_1d_7d_30d"`
AlertsResolution7dPlus30d int `json:"alerts_resolution_7d_plus_30d"`
AlertsRepeatOccurrences30d int `json:"alerts_repeat_occurrences_30d"`
AlertsSnoozedOccurrences30d int `json:"alerts_snoozed_occurrences_30d"`
AlertsResolvedWhileSnoozed30d int `json:"alerts_resolved_while_snoozed_30d"`
AlertManagerTenants int `json:"alert_manager_tenants"`
AlertDeliveryActiveTenants int `json:"alert_delivery_active_tenants"`
AlertFlappingEnabledTenants int `json:"alert_flapping_enabled_tenants"`
AlertIntentPolicyConfiguredTenants int `json:"alert_intent_policy_configured_tenants"`
AlertEventHistoryAuthoritativeTenants int `json:"alert_event_history_authoritative_tenants"`
AlertActiveStateAuthoritativeTenants int `json:"alert_active_state_authoritative_tenants"`
AlertActiveStatePersistenceDegradedTenants int `json:"alert_active_state_persistence_degraded_tenants"`
NotificationAttempts7d int `json:"notification_attempts_7d"`
NotificationDeliveries7d int `json:"notification_deliveries_7d"`
// NotificationFailures7d counts only terminal failed/dead-letter outcomes.
// Retry-attempt failures remain represented in NotificationAttempts7d.
NotificationFailures7d int `json:"notification_failures_7d"`
NotificationFailuresAuthentication7d int `json:"notification_failures_authentication_7d"`
NotificationFailuresRateLimited7d int `json:"notification_failures_rate_limited_7d"`
NotificationFailuresConnectivity7d int `json:"notification_failures_connectivity_7d"`
NotificationFailuresTLS7d int `json:"notification_failures_tls_7d"`
NotificationFailuresConfiguration7d int `json:"notification_failures_configuration_7d"`
NotificationFailuresRejected7d int `json:"notification_failures_rejected_7d"`
NotificationFailuresServerError7d int `json:"notification_failures_server_error_7d"`
NotificationFailuresUnknown7d int `json:"notification_failures_unknown_7d"`
// Pulse Intelligence usage (30-day counts/booleans — no prompts, commands, outputs, resource IDs, or token values)
PulseIntelligenceLoopConfigured bool `json:"pulse_intelligence_loop_configured"`
PulseIntelligenceLoopActive30d bool `json:"pulse_intelligence_loop_active_30d"`
PulseIntelligenceCompleteOperationsLoop30d bool `json:"pulse_intelligence_complete_operations_loop_30d"`
PulseIntelligenceApprovedExecutionLoop30d bool `json:"pulse_intelligence_approved_execution_loop_30d"`
PulseIntelligenceResolvedOperationsLoop30d bool `json:"pulse_intelligence_resolved_operations_loop_30d"`
PulseIntelligencePatrolControlCompletedOperationsLoop30d bool `json:"pulse_intelligence_patrol_control_completed_operations_loop_30d"`
PulseIntelligencePatrolControlResolvedOperationsLoop30d bool `json:"pulse_intelligence_patrol_control_resolved_operations_loop_30d"`
PulseIntelligencePatrolControlPaidCompletedOperationsLoop30d bool `json:"pulse_intelligence_patrol_control_paid_completed_operations_loop_30d"`
PulseIntelligencePatrolControlPaidResolvedOperationsLoop30d bool `json:"pulse_intelligence_patrol_control_paid_resolved_operations_loop_30d"`
PulseIntelligenceProActivationCompletedOperationsLoop30d bool `json:"pulse_intelligence_pro_activation_completed_operations_loop_30d"`
PulseIntelligenceProActivationResolvedOperationsLoop30d bool `json:"pulse_intelligence_pro_activation_resolved_operations_loop_30d"`
PulseIntelligenceProActivationPaidCompletedOperationsLoop30d bool `json:"pulse_intelligence_pro_activation_paid_completed_operations_loop_30d"`
PulseIntelligenceProActivationPaidResolvedOperationsLoop30d bool `json:"pulse_intelligence_pro_activation_paid_resolved_operations_loop_30d"`
PulseIntelligenceGovernedActionActive30d bool `json:"pulse_intelligence_governed_action_active_30d"`
PulseIntelligenceAssistantOperationsLoop30d bool `json:"pulse_intelligence_assistant_operations_loop_30d"`
PulseIntelligenceAssistantApprovedExecutionLoop30d bool `json:"pulse_intelligence_assistant_approved_execution_loop_30d"`
PulseIntelligenceAssistantApprovedActionSuccessLoop30d bool `json:"pulse_intelligence_assistant_approved_action_success_loop_30d"`
PulseIntelligenceAssistantResolvedOperationsLoop30d bool `json:"pulse_intelligence_assistant_resolved_operations_loop_30d"`
PulseIntelligenceExternalAgentOperationsLoop30d bool `json:"pulse_intelligence_external_agent_operations_loop_30d"`
PulseIntelligenceExternalAgentApprovedExecutionLoop30d bool `json:"pulse_intelligence_external_agent_approved_execution_loop_30d"`
PulseIntelligenceExternalAgentApprovedActionSuccessLoop30d bool `json:"pulse_intelligence_external_agent_approved_action_success_loop_30d"`
PulseIntelligenceExternalAgentResolvedOperationsLoop30d bool `json:"pulse_intelligence_external_agent_resolved_operations_loop_30d"`
PulseIntelligenceMCPAdapterOperationsLoop30d bool `json:"pulse_intelligence_mcp_adapter_operations_loop_30d"`
PulseIntelligenceMCPAdapterApprovedExecutionLoop30d bool `json:"pulse_intelligence_mcp_adapter_approved_execution_loop_30d"`
PulseIntelligenceMCPAdapterApprovedActionSuccessLoop30d bool `json:"pulse_intelligence_mcp_adapter_approved_action_success_loop_30d"`
PulseIntelligenceMCPAdapterResolvedOperationsLoop30d bool `json:"pulse_intelligence_mcp_adapter_resolved_operations_loop_30d"`
PulseIntelligenceOperationsLoopStarterRequests30d int `json:"pulse_intelligence_operations_loop_starter_requests_30d"`
PulseIntelligenceAssistantOperationsLoopStarterRequests30d int `json:"pulse_intelligence_assistant_operations_loop_starter_requests_30d"`
PulseIntelligencePatrolOperationsLoopStarterRequests30d int `json:"pulse_intelligence_patrol_operations_loop_starter_requests_30d"`
PulseIntelligencePatrolControlOperationsLoopStarterRequests30d int `json:"pulse_intelligence_patrol_control_operations_loop_starter_requests_30d"`
PulseIntelligenceProActivationOperationsLoopStarterRequests30d int `json:"pulse_intelligence_pro_activation_operations_loop_starter_requests_30d"`
PulseIntelligenceMCPOperationsLoopStarterRequests30d int `json:"pulse_intelligence_mcp_operations_loop_starter_requests_30d"`
PulseIntelligenceAssistantAICalls30d int `json:"pulse_intelligence_assistant_ai_calls_30d"`
PulseIntelligenceAssistantContextAICalls30d int `json:"pulse_intelligence_assistant_context_ai_calls_30d"`
PulseIntelligenceAssistantToolCalls30d int `json:"pulse_intelligence_assistant_tool_calls_30d"`
PulseIntelligencePatrolAICalls30d int `json:"pulse_intelligence_patrol_ai_calls_30d"`
PulseIntelligencePatrolRuns30d int `json:"pulse_intelligence_patrol_runs_30d"`
PulseIntelligencePatrolNewFindings30d int `json:"pulse_intelligence_patrol_new_findings_30d"`
PulseIntelligencePatrolInvestigations30d int `json:"pulse_intelligence_patrol_investigations_30d"`
PulseIntelligencePatrolResolvedFindings30d int `json:"pulse_intelligence_patrol_resolved_findings_30d"`
PulseIntelligencePatrolBlockedCause string `json:"pulse_intelligence_patrol_blocked_cause,omitempty"`
// Effective Patrol autonomy level after licence and Autopilot
// acknowledgement gating: monitor, approval, assisted, or full.
PulseIntelligencePatrolAutonomyLevel string `json:"pulse_intelligence_patrol_autonomy_level"`
// Coarse 30-day Patrol token volume from the local usage ledger. Closed
// buckets only; exact totals, prices, providers, and models stay local.
PulseIntelligencePatrolInputTokensBucket30d string `json:"pulse_intelligence_patrol_input_tokens_bucket_30d"`
PulseIntelligencePatrolOutputTokensBucket30d string `json:"pulse_intelligence_patrol_output_tokens_bucket_30d"`
// Investigation outcome partition of the findings counted by
// pulse_intelligence_patrol_investigations_30d. Each investigated finding
// is in exactly one bucket; the buckets sum to the investigation count.
PulseIntelligencePatrolInvestigationOutcomeFixVerified30d int `json:"pulse_intelligence_patrol_investigation_outcome_fix_verified_30d"`
PulseIntelligencePatrolInvestigationOutcomeFixQueued30d int `json:"pulse_intelligence_patrol_investigation_outcome_fix_queued_30d"`
PulseIntelligencePatrolInvestigationOutcomeFixExecuted30d int `json:"pulse_intelligence_patrol_investigation_outcome_fix_executed_30d"`
PulseIntelligencePatrolInvestigationOutcomeFixRejected30d int `json:"pulse_intelligence_patrol_investigation_outcome_fix_rejected_30d"`
PulseIntelligencePatrolInvestigationOutcomeFixFailed30d int `json:"pulse_intelligence_patrol_investigation_outcome_fix_failed_30d"`
PulseIntelligencePatrolInvestigationOutcomeFixVerificationUnknown30d int `json:"pulse_intelligence_patrol_investigation_outcome_fix_verification_unknown_30d"`
PulseIntelligencePatrolInvestigationOutcomeResolved30d int `json:"pulse_intelligence_patrol_investigation_outcome_resolved_30d"`
PulseIntelligencePatrolInvestigationOutcomeNeedsAttention30d int `json:"pulse_intelligence_patrol_investigation_outcome_needs_attention_30d"`
PulseIntelligencePatrolInvestigationOutcomeCannotFix30d int `json:"pulse_intelligence_patrol_investigation_outcome_cannot_fix_30d"`
PulseIntelligencePatrolInvestigationOutcomeTimedOut30d int `json:"pulse_intelligence_patrol_investigation_outcome_timed_out_30d"`
PulseIntelligencePatrolInvestigationOutcomeInProgress30d int `json:"pulse_intelligence_patrol_investigation_outcome_in_progress_30d"`
PulseIntelligencePatrolInvestigationOutcomeFailed30d int `json:"pulse_intelligence_patrol_investigation_outcome_failed_30d"`
PulseIntelligencePatrolInvestigationOutcomeOther30d int `json:"pulse_intelligence_patrol_investigation_outcome_other_30d"`
PulseIntelligenceExternalAgentEnabled bool `json:"pulse_intelligence_external_agent_enabled"`
PulseIntelligenceExternalAgentUsed30d bool `json:"pulse_intelligence_external_agent_used_30d"`
PulseIntelligenceMCPAdapterUsed30d bool `json:"pulse_intelligence_mcp_adapter_used_30d"`
PulseIntelligenceExternalAgentContextRequests30d int `json:"pulse_intelligence_external_agent_context_requests_30d"`
PulseIntelligenceExternalAgentEventStreamRequests30d int `json:"pulse_intelligence_external_agent_event_stream_requests_30d"`
PulseIntelligenceExternalAgentHistoryReadDays30d int `json:"pulse_intelligence_external_agent_history_read_days_30d"`
PulseIntelligenceExternalAgentProvisioningRequests30d int `json:"pulse_intelligence_external_agent_provisioning_requests_30d"`
PulseIntelligenceExternalAgentOperatorStateRequests30d int `json:"pulse_intelligence_external_agent_operator_state_requests_30d"`
PulseIntelligenceExternalAgentFindingRequests30d int `json:"pulse_intelligence_external_agent_finding_requests_30d"`
PulseIntelligenceExternalAgentActionRequests30d int `json:"pulse_intelligence_external_agent_action_requests_30d"`
PulseIntelligenceActionPlans30d int `json:"pulse_intelligence_action_plans_30d"`
PulseIntelligenceApprovalRequests30d int `json:"pulse_intelligence_approval_requests_30d"`
PulseIntelligenceRejectedActionDecisions30d int `json:"pulse_intelligence_rejected_action_decisions_30d"`
PulseIntelligenceApprovedActionDecisions30d int `json:"pulse_intelligence_approved_action_decisions_30d"`
PulseIntelligenceApprovedActionAttempts30d int `json:"pulse_intelligence_approved_action_attempts_30d"`
PulseIntelligenceApprovedActionSuccesses30d int `json:"pulse_intelligence_approved_action_successes_30d"`
PulseIntelligencePatrolActionPlans30d int `json:"pulse_intelligence_patrol_action_plans_30d"`
PulseIntelligencePatrolApprovalRequests30d int `json:"pulse_intelligence_patrol_approval_requests_30d"`
PulseIntelligencePatrolRejectedActionDecisions30d int `json:"pulse_intelligence_patrol_rejected_action_decisions_30d"`
PulseIntelligencePatrolApprovedActionDecisions30d int `json:"pulse_intelligence_patrol_approved_action_decisions_30d"`
PulseIntelligencePatrolApprovedActionAttempts30d int `json:"pulse_intelligence_patrol_approved_action_attempts_30d"`
PulseIntelligencePatrolApprovedActionSuccesses30d int `json:"pulse_intelligence_patrol_approved_action_successes_30d"`
// Cause-coded approved-action failure counters. Together with successes
// and still-in-flight attempts these partition the attempt count, so the
// attempt/success gap is attributable without exporting action content.
PulseIntelligenceApprovedActionFailuresPreDispatch30d int `json:"pulse_intelligence_approved_action_failures_pre_dispatch_30d"`
PulseIntelligenceApprovedActionFailuresExecution30d int `json:"pulse_intelligence_approved_action_failures_execution_30d"`
PulseIntelligenceApprovedActionFailuresUnverified30d int `json:"pulse_intelligence_approved_action_failures_unverified_30d"`
PulseIntelligenceApprovedActionStuckExecuting30d int `json:"pulse_intelligence_approved_action_stuck_executing_30d"`
PulseIntelligenceApprovedActionInFlight30d int `json:"pulse_intelligence_approved_action_in_flight_30d"`
PulseIntelligenceApprovedActionUnclassified30d int `json:"pulse_intelligence_approved_action_unclassified_30d"`
PulseIntelligenceApprovedActionRefusalsPlanStale30d int `json:"pulse_intelligence_approved_action_refusals_plan_stale_30d"`
PulseIntelligenceApprovedActionRefusalsPolicy30d int `json:"pulse_intelligence_approved_action_refusals_policy_30d"`
PulseIntelligenceApprovedActionRefusalsCapability30d int `json:"pulse_intelligence_approved_action_refusals_capability_30d"`
PulseIntelligenceApprovedActionRefusalsTargetChanged30d int `json:"pulse_intelligence_approved_action_refusals_target_changed_30d"`
PulseIntelligenceApprovedActionRefusalsPrerequisite30d int `json:"pulse_intelligence_approved_action_refusals_prerequisite_30d"`
PulseIntelligenceApprovedActionRefusalsContract30d int `json:"pulse_intelligence_approved_action_refusals_contract_30d"`
PulseIntelligenceApprovedActionRefusalsUncoded30d int `json:"pulse_intelligence_approved_action_refusals_uncoded_30d"`
PulseIntelligenceApprovedActionRefusalsOther30d int `json:"pulse_intelligence_approved_action_refusals_other_30d"`
PulseIntelligenceVerifiedFindingResolutions30d int `json:"pulse_intelligence_verified_finding_resolutions_30d"`
PulseIntelligenceApprovedActionLastFailureReason30d string `json:"pulse_intelligence_approved_action_last_failure_reason_30d,omitempty"`
// Content-free reporting outcomes and measurement coverage.
ReportingGeneratedResources30d int `json:"reporting_generated_resources_30d"`
ReportingGeneratedVMInventory30d int `json:"reporting_generated_vm_inventory_30d"`
ReportingGeneratedBackupHealth30d int `json:"reporting_generated_backup_health_30d"`
ReportingGeneratedPatrolDigest30d int `json:"reporting_generated_patrol_digest_30d"`
ReportingSuccessfulResourcesPeriods30d int `json:"reporting_successful_resources_periods_30d"`
ReportingSuccessfulVMInventoryPeriods30d int `json:"reporting_successful_vm_inventory_periods_30d"`
ReportingSuccessfulBackupHealthPeriods30d int `json:"reporting_successful_backup_health_periods_30d"`
ReportingSuccessfulPatrolDigestPeriods30d int `json:"reporting_successful_patrol_digest_periods_30d"`
ReportingRepeatResourcesPeriods30d int `json:"reporting_repeat_resources_periods_30d"`
ReportingRepeatVMInventoryPeriods30d int `json:"reporting_repeat_vm_inventory_periods_30d"`
ReportingRepeatBackupHealthPeriods30d int `json:"reporting_repeat_backup_health_periods_30d"`
ReportingRepeatPatrolDigestPeriods30d int `json:"reporting_repeat_patrol_digest_periods_30d"`
ReportingSaved30d int `json:"reporting_saved_30d"`
ReportingEmailAccepted30d int `json:"reporting_email_accepted_30d"`
ReportingDeliveryFailed30d int `json:"reporting_delivery_failed_30d"`
ReportingDeliveryUnconfirmed30d int `json:"reporting_delivery_unconfirmed_30d"`
ReportingDataComplete30d int `json:"reporting_data_complete_30d"`
ReportingDataIncomplete30d int `json:"reporting_data_incomplete_30d"`
ReportingDataUnavailable30d int `json:"reporting_data_unavailable_30d"`
ReportingTelemetryAvailable bool `json:"reporting_telemetry_available"`
ReportingObservationDays int `json:"reporting_observation_days"`
ReportingResourcesActiveDays30d int `json:"reporting_resources_active_days_30d"`
ReportingVMInventoryActiveDays30d int `json:"reporting_vm_inventory_active_days_30d"`
ReportingBackupHealthActiveDays30d int `json:"reporting_backup_health_active_days_30d"`
ReportingPatrolDigestActiveDays30d int `json:"reporting_patrol_digest_active_days_30d"`
}
// Snapshot holds the dynamic state gathered at ping time.
// The telemetry package calls a user-provided SnapshotFunc to populate this,
// keeping the package decoupled from monitor/config internals.
type Snapshot struct {
PVENodes int
PBSInstances int
PMGInstances int
VMs int
Containers int
AgentHosts int
DockerHosts int
DockerContainers int
KubernetesClusters int
KubernetesNodes int
KubernetesPods int
KubernetesDeployments int
StoragePools int
PhysicalDisks int
CephClusters int
NetworkShares int
TrueNASSystems int
TrueNASVMs int
TrueNASApps int
VMwareHosts int
VMwareVMs int
VMwareDatastores int
AvailabilityTargets int
AvailabilityProbeTargets int
AvailabilityProbeAgents int
AIEnabled bool
PatrolEnabled bool
DiscoveryEnabled bool
NotificationsEnabled bool
AIActionsEnabled bool
AlertAIEnabled bool
AIProviderClass string
ActiveAlerts int
RelayEnabled bool
SSOEnabled bool
MultiTenant bool
PaidLicense bool
HasAPITokens bool
RBACCustomRoles int
RBACUserAssignments int
AuditReads30d int
ReportSchedules int
ReportSchedulesEnabled int
ReportSchedulesRun30d int
AgentProfiles int
UpdateAttempts30d int
UpdateSuccesses30d int
UpdateFailures30d int
UpdateLastFailureCategory string
UpdateChannel string
UpdateCheckOutcome string
UpdateAvailable bool
NodeTestAttempts30d int
NodeTestFailures30d int
SuccessfulHistoryReadsOver7d30d int
WorkloadHistoryPreviewSessions30d int
WorkloadHistoryScrubSessions30d int
WorkloadHistoryRangeChangeSessions30d int
WorkloadHistoryDetailsSelectionSessions30d int
AuthConfigured bool
ConfiguredConnections int
AlertsFired30d int
AlertsAcknowledged30d int
AlertsResolved30d int
ActiveAlertsInfo int
ActiveAlertsWarning int
ActiveAlertsCritical int
ActiveAlertsAgeUnder1h int
ActiveAlertsAge1h24h int
ActiveAlertsAge1d7d int
ActiveAlertsAge7dPlus int
AlertsFiredInfo30d int
AlertsFiredWarning30d int
AlertsFiredCritical30d int
AlertsResolvedInfo30d int
AlertsResolvedWarning30d int
AlertsResolvedCritical30d int
AlertsResolutionUnder15m30d int
AlertsResolution15m1h30d int
AlertsResolution1h24h30d int
AlertsResolution1d7d30d int
AlertsResolution7dPlus30d int
AlertsRepeatOccurrences30d int
AlertsSnoozedOccurrences30d int
AlertsResolvedWhileSnoozed30d int
AlertManagerTenants int
AlertDeliveryActiveTenants int
AlertFlappingEnabledTenants int
AlertIntentPolicyConfiguredTenants int
AlertEventHistoryAuthoritativeTenants int
AlertActiveStateAuthoritativeTenants int
AlertActiveStatePersistenceDegradedTenants int
NotificationAttempts7d int
NotificationDeliveries7d int
NotificationFailures7d int
NotificationFailuresAuthentication7d int
NotificationFailuresRateLimited7d int
NotificationFailuresConnectivity7d int
NotificationFailuresTLS7d int
NotificationFailuresConfiguration7d int
NotificationFailuresRejected7d int
NotificationFailuresServerError7d int
NotificationFailuresUnknown7d int
PulseIntelligenceLoopConfigured bool
PulseIntelligenceLoopActive30d bool
PulseIntelligenceCompleteOperationsLoop30d bool
PulseIntelligenceApprovedExecutionLoop30d bool
PulseIntelligenceResolvedOperationsLoop30d bool
PulseIntelligencePatrolControlCompletedOperationsLoop30d bool
PulseIntelligencePatrolControlResolvedOperationsLoop30d bool
PulseIntelligencePatrolControlPaidCompletedOperationsLoop30d bool
PulseIntelligencePatrolControlPaidResolvedOperationsLoop30d bool
PulseIntelligenceProActivationCompletedOperationsLoop30d bool
PulseIntelligenceProActivationResolvedOperationsLoop30d bool
PulseIntelligenceProActivationPaidCompletedOperationsLoop30d bool
PulseIntelligenceProActivationPaidResolvedOperationsLoop30d bool
PulseIntelligenceGovernedActionActive30d bool
PulseIntelligenceAssistantOperationsLoop30d bool
PulseIntelligenceAssistantApprovedExecutionLoop30d bool
PulseIntelligenceAssistantApprovedActionSuccessLoop30d bool
PulseIntelligenceAssistantResolvedOperationsLoop30d bool
PulseIntelligenceExternalAgentOperationsLoop30d bool
PulseIntelligenceExternalAgentApprovedExecutionLoop30d bool
PulseIntelligenceExternalAgentApprovedActionSuccessLoop30d bool
PulseIntelligenceExternalAgentResolvedOperationsLoop30d bool
PulseIntelligenceMCPAdapterOperationsLoop30d bool
PulseIntelligenceMCPAdapterApprovedExecutionLoop30d bool
PulseIntelligenceMCPAdapterApprovedActionSuccessLoop30d bool
PulseIntelligenceMCPAdapterResolvedOperationsLoop30d bool
PulseIntelligenceOperationsLoopStarterRequests30d int
PulseIntelligenceAssistantOperationsLoopStarterRequests30d int
PulseIntelligencePatrolOperationsLoopStarterRequests30d int
PulseIntelligencePatrolControlOperationsLoopStarterRequests30d int
PulseIntelligenceProActivationOperationsLoopStarterRequests30d int
PulseIntelligenceMCPOperationsLoopStarterRequests30d int
PulseIntelligenceAssistantAICalls30d int
PulseIntelligenceAssistantContextAICalls30d int
PulseIntelligenceAssistantToolCalls30d int
PulseIntelligencePatrolAICalls30d int
PulseIntelligencePatrolRuns30d int
PulseIntelligencePatrolNewFindings30d int
PulseIntelligencePatrolInvestigations30d int
PulseIntelligencePatrolResolvedFindings30d int
PulseIntelligencePatrolBlockedCause string
PulseIntelligencePatrolAutonomyLevel string
PulseIntelligencePatrolInputTokensBucket30d string
PulseIntelligencePatrolOutputTokensBucket30d string
PulseIntelligencePatrolInvestigationOutcomeFixVerified30d int
PulseIntelligencePatrolInvestigationOutcomeFixQueued30d int
PulseIntelligencePatrolInvestigationOutcomeFixExecuted30d int
PulseIntelligencePatrolInvestigationOutcomeFixRejected30d int
PulseIntelligencePatrolInvestigationOutcomeFixFailed30d int
PulseIntelligencePatrolInvestigationOutcomeFixVerificationUnknown30d int
PulseIntelligencePatrolInvestigationOutcomeResolved30d int
PulseIntelligencePatrolInvestigationOutcomeNeedsAttention30d int
PulseIntelligencePatrolInvestigationOutcomeCannotFix30d int
PulseIntelligencePatrolInvestigationOutcomeTimedOut30d int
PulseIntelligencePatrolInvestigationOutcomeInProgress30d int
PulseIntelligencePatrolInvestigationOutcomeFailed30d int
PulseIntelligencePatrolInvestigationOutcomeOther30d int
PulseIntelligenceExternalAgentEnabled bool
PulseIntelligenceExternalAgentOperationsLoopReady bool
PulseIntelligenceExternalAgentUsed30d bool
PulseIntelligenceMCPAdapterUsed30d bool
PulseIntelligenceExternalAgentContextRequests30d int
PulseIntelligenceExternalAgentEventStreamRequests30d int
PulseIntelligenceExternalAgentHistoryReadDays30d int
PulseIntelligenceExternalAgentProvisioningRequests30d int
PulseIntelligenceExternalAgentOperatorStateRequests30d int
PulseIntelligenceExternalAgentFindingRequests30d int
PulseIntelligenceExternalAgentActionRequests30d int
PulseIntelligenceActionPlans30d int
PulseIntelligenceApprovalRequests30d int
PulseIntelligenceRejectedActionDecisions30d int
PulseIntelligenceApprovedActionDecisions30d int
PulseIntelligenceApprovedActionAttempts30d int
PulseIntelligenceApprovedActionSuccesses30d int
PulseIntelligencePatrolActionPlans30d int
PulseIntelligencePatrolApprovalRequests30d int
PulseIntelligencePatrolRejectedActionDecisions30d int
PulseIntelligencePatrolApprovedActionDecisions30d int
PulseIntelligencePatrolApprovedActionAttempts30d int
PulseIntelligencePatrolApprovedActionSuccesses30d int
PulseIntelligenceApprovedActionFailuresPreDispatch30d int
PulseIntelligenceApprovedActionFailuresExecution30d int
PulseIntelligenceApprovedActionFailuresUnverified30d int
PulseIntelligenceApprovedActionStuckExecuting30d int
PulseIntelligenceApprovedActionInFlight30d int
PulseIntelligenceApprovedActionUnclassified30d int
PulseIntelligenceApprovedActionRefusalsPlanStale30d int
PulseIntelligenceApprovedActionRefusalsPolicy30d int
PulseIntelligenceApprovedActionRefusalsCapability30d int
PulseIntelligenceApprovedActionRefusalsTargetChanged30d int
PulseIntelligenceApprovedActionRefusalsPrerequisite30d int
PulseIntelligenceApprovedActionRefusalsContract30d int
PulseIntelligenceApprovedActionRefusalsUncoded30d int
PulseIntelligenceApprovedActionRefusalsOther30d int
PulseIntelligenceVerifiedFindingResolutions30d int
PulseIntelligenceApprovedActionLastFailureReason30d string
// Content-free reporting outcomes and measurement coverage.
ReportingGeneratedResources30d int
ReportingGeneratedVMInventory30d int
ReportingGeneratedBackupHealth30d int
ReportingGeneratedPatrolDigest30d int
ReportingSuccessfulResourcesPeriods30d int
ReportingSuccessfulVMInventoryPeriods30d int
ReportingSuccessfulBackupHealthPeriods30d int
ReportingSuccessfulPatrolDigestPeriods30d int
ReportingRepeatResourcesPeriods30d int
ReportingRepeatVMInventoryPeriods30d int
ReportingRepeatBackupHealthPeriods30d int
ReportingRepeatPatrolDigestPeriods30d int
ReportingSaved30d int
ReportingEmailAccepted30d int
ReportingDeliveryFailed30d int
ReportingDeliveryUnconfirmed30d int
ReportingDataComplete30d int
ReportingDataIncomplete30d int
ReportingDataUnavailable30d int
ReportingTelemetryAvailable bool
ReportingObservationDays int
ReportingResourcesActiveDays30d int
ReportingVMInventoryActiveDays30d int
ReportingBackupHealthActiveDays30d int
ReportingPatrolDigestActiveDays30d int
}
// PulseIntelligenceActionSnapshot is the action-governance portion of the
// Pulse Intelligence telemetry loop. It is intentionally count-only so callers
// can aggregate local audit records without exporting action details. The
// failure-cause fields carry only closed machine reason codes, never command
// text, resource identifiers, or output.
type PulseIntelligenceActionSnapshot struct {
ActionPlans30d int
ApprovalRequests30d int
RejectedActionDecisions30d int
ApprovedActionDecisions30d int
ApprovedActionAttempts30d int
ApprovedActionSuccesses30d int
PatrolActionPlans30d int
PatrolApprovalRequests30d int
PatrolRejectedActionDecisions30d int
PatrolApprovedActionDecisions30d int
PatrolApprovedActionAttempts30d int
PatrolApprovedActionSuccesses30d int
// ApprovedActionFailuresPreDispatch30d counts approved attempts refused
// terminally before dispatch (plan drift, expiry, emergency stop, policy
// authorization).
ApprovedActionFailuresPreDispatch30d int
// ApprovedActionFailuresExecution30d counts approved attempts whose
// dispatched execution failed or ended inconclusive.
ApprovedActionFailuresExecution30d int
// ApprovedActionFailuresUnverified30d counts approved attempts that
// completed execution but whose outcome verification was not confirmed.
ApprovedActionFailuresUnverified30d int
// ApprovedActionStuckExecuting30d counts approved attempts still in the
// executing state well past any legitimate dispatch window.
ApprovedActionStuckExecuting30d int
// ApprovedActionInFlight30d counts approved attempts still inside the
// legitimate dispatch window.
ApprovedActionInFlight30d int
// ApprovedActionUnclassified30d counts approved attempts whose
// authoritative audit is missing or cannot be mapped to a terminal class.
ApprovedActionUnclassified30d int
// The refusal category counters partition ApprovedActionFailuresPreDispatch30d
// into stable, content-free operator diagnostics.
ApprovedActionRefusalsPlanStale30d int
ApprovedActionRefusalsPolicy30d int
ApprovedActionRefusalsCapability30d int
ApprovedActionRefusalsTargetChanged30d int
ApprovedActionRefusalsPrerequisite30d int
ApprovedActionRefusalsContract30d int
// ApprovedActionRefusalsUncoded30d counts pre-dispatch refusals that
// arrived without any machine reason code, which is what an agent older
// than the typed refusal contract reports.
ApprovedActionRefusalsUncoded30d int
ApprovedActionRefusalsOther30d int
// VerifiedFindingResolutions30d counts completed, approved Patrol-origin
// actions whose postcondition was independently confirmed. No finding or
// action identity leaves the runtime.
VerifiedFindingResolutions30d int
// ApprovedActionLastFailureReason30d is the machine reason code of the
// most recent approved-action failure, sanitized to a closed code shape.
ApprovedActionLastFailureReason30d string
// PatrolBlockedCause is the fixed machine cause code exported only while
// an enabled Patrol is in the blocked runtime state. Blocked-reason text,
// provider endpoints, model names, and configuration never leave the
// runtime.
PatrolBlockedCause string
// PatrolAutonomyLevel is the effective Patrol autonomy level after
// licence and Autopilot acknowledgement gating: monitor, approval,
// assisted, or full. Empty means the AI service was unavailable and the
// sender reports monitor.
PatrolAutonomyLevel string
}
// ApplyUpdateTelemetrySnapshot adds content-free update funnel counters from
// local update history. It reports only aggregate counts and one coarse failure
// category over the install-ID rotation window.
func ApplyUpdateTelemetrySnapshot(s *Snapshot, history *updates.UpdateHistory, now time.Time) {
if s == nil || history == nil {
return
}
if now.IsZero() {
now = time.Now()
}
since := now.UTC().Add(-installIDRotationWindow)
entries := history.ListEntries(updates.HistoryFilter{Action: "update"})
var lastFailure *updates.UpdateHistoryEntry
for i := range entries {
entry := entries[i]
if entry.Timestamp.IsZero() || entry.Timestamp.UTC().Before(since) {
continue
}
s.UpdateAttempts30d++
switch entry.Status {
case updates.StatusSuccess:
s.UpdateSuccesses30d++
case updates.StatusFailed, updates.StatusRolledBack:
s.UpdateFailures30d++
if lastFailure == nil || entry.Timestamp.After(lastFailure.Timestamp) {
candidate := entry
lastFailure = &candidate
}
}
}
if lastFailure != nil {
s.UpdateLastFailureCategory = classifyUpdateFailureCategory(*lastFailure)
}
}
// ApplyUpdateCheckTelemetrySnapshot adds the effective update channel and the
// closed outcome of the last check on it. Only fixed categories leave the
// instance; versions, URLs, and error text never do.
func ApplyUpdateCheckTelemetrySnapshot(s *Snapshot, check updates.UpdateCheckObservation) {
if s == nil {
return
}
s.UpdateChannel = normalizeUpdateChannelForTelemetry(check.Channel)
s.UpdateCheckOutcome = normalizeUpdateCheckOutcomeForTelemetry(check.Outcome)
s.UpdateAvailable = check.Available && s.UpdateCheckOutcome == updates.UpdateCheckOutcomeAvailable
}
func normalizeUpdateChannelForTelemetry(channel string) string {
switch strings.ToLower(strings.TrimSpace(channel)) {
case "stable":
return "stable"
case "rc":
return "rc"
default:
return "unknown"
}
}
func normalizeUpdateCheckOutcomeForTelemetry(outcome string) string {
switch outcome = strings.ToLower(strings.TrimSpace(outcome)); outcome {
case updates.UpdateCheckOutcomeNotChecked,
updates.UpdateCheckOutcomeUpToDate,
updates.UpdateCheckOutcomeAvailable,
updates.UpdateCheckOutcomeNoRelease,
updates.UpdateCheckOutcomeRateLimited,
updates.UpdateCheckOutcomeNetworkError,
updates.UpdateCheckOutcomeMetadataError,
updates.UpdateCheckOutcomeSkipped,
updates.UpdateCheckOutcomeError:
return outcome
case "":
return updates.UpdateCheckOutcomeNotChecked
default:
return updates.UpdateCheckOutcomeError
}
}
func classifyUpdateFailureCategory(entry updates.UpdateHistoryEntry) string {
switch entry.Status {
case updates.StatusRolledBack:
return "rolled_back"
case updates.StatusCancelled:
return "cancelled"
}
text := ""
if entry.Error != nil {
text = strings.ToLower(strings.TrimSpace(entry.Error.Code + " " + entry.Error.Message + " " + entry.Error.Details))
}
switch {
case strings.Contains(text, "signature"):
return "signature"
case strings.Contains(text, "checksum"):
return "checksum"
case strings.Contains(text, "download"):
return "download"
case strings.Contains(text, "disk space") || strings.Contains(text, "insufficient disk"):
return "disk_space"
case strings.Contains(text, "extract") || strings.Contains(text, "archive"):
return "extract"
case strings.Contains(text, "backup"):
return "backup"
case strings.Contains(text, "apply"):
return "apply"
case strings.Contains(text, "restart"):
return "restart"
default:
return "unknown"
}
}