Files
pulse/pkg/server/telemetry_pulse_intelligence.go
rcourtman 9316507df0 Preserve Patrol finding volume beyond the history cap
New-finding telemetry lost older activity once the run history reached its
100-entry cap. Persist a bounded daily finding tally with a separate
upgrade cursor, preserving run counts while backfilling retained findings.

Cover restart, repeated saves, upgrade, read failure and UTC-day retention.
Record the measurement boundary and retire the resolved coverage gap.
2026-09-05 19:48:49 +01:00

448 lines
21 KiB
Go

package server
import (
"strings"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/agentcapabilities"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/telemetry"
)
func applyPulseIntelligenceTelemetrySnapshot(
snap *telemetry.Snapshot,
persistence *config.ConfigPersistence,
currentCfg *config.Config,
actionSnapshot telemetry.PulseIntelligenceActionSnapshot,
now time.Time,
) {
if snap == nil {
return
}
if now.IsZero() {
now = time.Now()
}
now = now.UTC()
since := now.Add(-telemetry.PulseIntelligenceTelemetryWindow)
// Schema v17 closed-vocabulary defaults. A missing usage ledger or AI
// service must still export a valid bucket, never an empty string.
if snap.AIProviderClass == "" {
snap.AIProviderClass = telemetry.AIProviderClassNone
}
snap.PulseIntelligencePatrolAutonomyLevel = telemetry.NormalizePatrolAutonomyLevelForTelemetry(actionSnapshot.PatrolAutonomyLevel)
snap.PulseIntelligencePatrolInputTokensBucket30d = telemetry.PatrolTokenBucketZero
snap.PulseIntelligencePatrolOutputTokensBucket30d = telemetry.PatrolTokenBucketZero
applyPulseIntelligenceAIUsageSnapshot(snap, persistence, since)
applyPulseIntelligencePatrolRunSnapshot(snap, persistence, since)
applyPulseIntelligenceFindingSnapshot(snap, persistence, since)
applyPulseIntelligenceExternalAgentSnapshot(snap, persistence, currentCfg, since, now)
applyPulseIntelligenceWorkflowPromptSnapshot(snap, persistence, since)
snap.PulseIntelligenceActionPlans30d = actionSnapshot.ActionPlans30d
snap.PulseIntelligenceApprovalRequests30d = actionSnapshot.ApprovalRequests30d
snap.PulseIntelligenceRejectedActionDecisions30d = actionSnapshot.RejectedActionDecisions30d
snap.PulseIntelligenceApprovedActionDecisions30d = actionSnapshot.ApprovedActionDecisions30d
snap.PulseIntelligenceApprovedActionAttempts30d = actionSnapshot.ApprovedActionAttempts30d
snap.PulseIntelligenceApprovedActionSuccesses30d = actionSnapshot.ApprovedActionSuccesses30d
snap.PulseIntelligencePatrolActionPlans30d = actionSnapshot.PatrolActionPlans30d
snap.PulseIntelligencePatrolApprovalRequests30d = actionSnapshot.PatrolApprovalRequests30d
snap.PulseIntelligencePatrolRejectedActionDecisions30d = actionSnapshot.PatrolRejectedActionDecisions30d
snap.PulseIntelligencePatrolApprovedActionDecisions30d = actionSnapshot.PatrolApprovedActionDecisions30d
snap.PulseIntelligencePatrolApprovedActionAttempts30d = actionSnapshot.PatrolApprovedActionAttempts30d
snap.PulseIntelligencePatrolApprovedActionSuccesses30d = actionSnapshot.PatrolApprovedActionSuccesses30d
snap.PulseIntelligenceApprovedActionFailuresPreDispatch30d = actionSnapshot.ApprovedActionFailuresPreDispatch30d
snap.PulseIntelligenceApprovedActionFailuresExecution30d = actionSnapshot.ApprovedActionFailuresExecution30d
snap.PulseIntelligenceApprovedActionFailuresUnverified30d = actionSnapshot.ApprovedActionFailuresUnverified30d
snap.PulseIntelligenceApprovedActionStuckExecuting30d = actionSnapshot.ApprovedActionStuckExecuting30d
snap.PulseIntelligenceApprovedActionInFlight30d = actionSnapshot.ApprovedActionInFlight30d
snap.PulseIntelligenceApprovedActionUnclassified30d = actionSnapshot.ApprovedActionUnclassified30d
snap.PulseIntelligenceApprovedActionRefusalsPlanStale30d = actionSnapshot.ApprovedActionRefusalsPlanStale30d
snap.PulseIntelligenceApprovedActionRefusalsPolicy30d = actionSnapshot.ApprovedActionRefusalsPolicy30d
snap.PulseIntelligenceApprovedActionRefusalsCapability30d = actionSnapshot.ApprovedActionRefusalsCapability30d
snap.PulseIntelligenceApprovedActionRefusalsTargetChanged30d = actionSnapshot.ApprovedActionRefusalsTargetChanged30d
snap.PulseIntelligenceApprovedActionRefusalsPrerequisite30d = actionSnapshot.ApprovedActionRefusalsPrerequisite30d
snap.PulseIntelligenceApprovedActionRefusalsContract30d = actionSnapshot.ApprovedActionRefusalsContract30d
snap.PulseIntelligenceApprovedActionRefusalsUncoded30d = actionSnapshot.ApprovedActionRefusalsUncoded30d
snap.PulseIntelligenceApprovedActionRefusalsOther30d = actionSnapshot.ApprovedActionRefusalsOther30d
snap.PulseIntelligenceVerifiedFindingResolutions30d = actionSnapshot.VerifiedFindingResolutions30d
snap.PulseIntelligenceApprovedActionLastFailureReason30d = actionSnapshot.ApprovedActionLastFailureReason30d
snap.PulseIntelligencePatrolBlockedCause = actionSnapshot.PatrolBlockedCause
applyPulseIntelligenceAdoptionSnapshot(snap)
}
func applyPulseIntelligenceAdoptionSnapshot(snap *telemetry.Snapshot) {
if snap == nil {
return
}
snap.PulseIntelligenceLoopConfigured = snap.AIEnabled ||
snap.PatrolEnabled ||
snap.AIActionsEnabled ||
snap.PulseIntelligenceExternalAgentEnabled
snap.PulseIntelligenceGovernedActionActive30d =
snap.PulseIntelligenceActionPlans30d > 0 ||
snap.PulseIntelligenceApprovalRequests30d > 0 ||
snap.PulseIntelligenceRejectedActionDecisions30d > 0 ||
snap.PulseIntelligenceApprovedActionDecisions30d > 0 ||
snap.PulseIntelligenceApprovedActionAttempts30d > 0
patrolActive := snap.PulseIntelligencePatrolAICalls30d > 0 ||
snap.PulseIntelligencePatrolRuns30d > 0 ||
snap.PulseIntelligencePatrolNewFindings30d > 0 ||
snap.PulseIntelligencePatrolInvestigations30d > 0 ||
snap.PulseIntelligencePatrolResolvedFindings30d > 0
patrolIssueEvidenceCount := snap.PulseIntelligencePatrolNewFindings30d +
snap.PulseIntelligencePatrolInvestigations30d +
snap.PulseIntelligencePatrolResolvedFindings30d
patrolIssueEvidenceActive := patrolIssueEvidenceCount > 0
assistantCollaborationCount := snap.PulseIntelligenceAssistantContextAICalls30d +
snap.PulseIntelligenceAssistantToolCalls30d
assistantCollaborationActive := assistantCollaborationCount > 0
externalAgentCollaborationActive := pulseIntelligenceExternalAgentActivity(snap)
externalAgentCollaborationCount := 0
if externalAgentCollaborationActive {
externalAgentCollaborationCount = 1
}
mcpAdapterCollaborationActive := snap.PulseIntelligenceMCPAdapterUsed30d
contextualCollaborationCount := assistantCollaborationCount + externalAgentCollaborationCount
contextualCollaborationActive := contextualCollaborationCount > 0
governedActionDecisionActive := snap.PulseIntelligenceRejectedActionDecisions30d > 0 ||
snap.PulseIntelligenceApprovedActionDecisions30d > 0 ||
snap.PulseIntelligenceApprovedActionAttempts30d > 0
approvedExecutionActive := snap.PulseIntelligenceApprovedActionAttempts30d > 0
approvedSuccessActive := snap.PulseIntelligenceApprovedActionSuccesses30d > 0
verifiedFindingResolutionActive := snap.PulseIntelligenceVerifiedFindingResolutions30d > 0
patrolControlProof := telemetry.ClassifyPulseIntelligencePatrolControlProof(telemetry.PulseIntelligencePatrolControlProofInput{
PatrolControlStarterCount: snap.PulseIntelligencePatrolControlOperationsLoopStarterRequests30d,
PatrolIssueEvidenceCount: patrolIssueEvidenceCount,
ContextualCollaborationCount: contextualCollaborationCount,
ApprovedDecisionCount: snap.PulseIntelligenceApprovedActionDecisions30d,
RejectedDecisionCount: snap.PulseIntelligenceRejectedActionDecisions30d,
VerifiedOutcomeCount: snap.PulseIntelligenceVerifiedFindingResolutions30d,
})
snap.PulseIntelligenceCompleteOperationsLoop30d =
patrolIssueEvidenceActive &&
contextualCollaborationActive &&
governedActionDecisionActive
snap.PulseIntelligenceApprovedExecutionLoop30d =
patrolIssueEvidenceActive &&
contextualCollaborationActive &&
approvedExecutionActive
snap.PulseIntelligenceResolvedOperationsLoop30d =
verifiedFindingResolutionActive
snap.PulseIntelligencePatrolControlCompletedOperationsLoop30d = patrolControlProof.Completed
snap.PulseIntelligencePatrolControlResolvedOperationsLoop30d = patrolControlProof.Resolved
snap.PulseIntelligencePatrolControlPaidCompletedOperationsLoop30d =
snap.PaidLicense && snap.PulseIntelligencePatrolControlCompletedOperationsLoop30d
snap.PulseIntelligencePatrolControlPaidResolvedOperationsLoop30d =
snap.PaidLicense && snap.PulseIntelligencePatrolControlResolvedOperationsLoop30d
// Legacy Pro activation cohort fields mirror Patrol control for continuity.
snap.PulseIntelligenceProActivationCompletedOperationsLoop30d = snap.PulseIntelligencePatrolControlCompletedOperationsLoop30d
snap.PulseIntelligenceProActivationResolvedOperationsLoop30d = snap.PulseIntelligencePatrolControlResolvedOperationsLoop30d
snap.PulseIntelligenceProActivationPaidCompletedOperationsLoop30d =
snap.PulseIntelligencePatrolControlPaidCompletedOperationsLoop30d
snap.PulseIntelligenceProActivationPaidResolvedOperationsLoop30d =
snap.PulseIntelligencePatrolControlPaidResolvedOperationsLoop30d
snap.PulseIntelligenceAssistantOperationsLoop30d =
patrolIssueEvidenceActive &&
assistantCollaborationActive &&
governedActionDecisionActive
snap.PulseIntelligenceAssistantApprovedExecutionLoop30d =
patrolIssueEvidenceActive &&
assistantCollaborationActive &&
approvedExecutionActive
snap.PulseIntelligenceAssistantApprovedActionSuccessLoop30d =
patrolIssueEvidenceActive &&
assistantCollaborationActive &&
approvedSuccessActive
snap.PulseIntelligenceAssistantResolvedOperationsLoop30d =
verifiedFindingResolutionActive &&
assistantCollaborationActive
snap.PulseIntelligenceExternalAgentOperationsLoop30d =
patrolIssueEvidenceActive &&
externalAgentCollaborationActive &&
governedActionDecisionActive
snap.PulseIntelligenceExternalAgentApprovedExecutionLoop30d =
patrolIssueEvidenceActive &&
externalAgentCollaborationActive &&
approvedExecutionActive
snap.PulseIntelligenceExternalAgentApprovedActionSuccessLoop30d =
patrolIssueEvidenceActive &&
externalAgentCollaborationActive &&
approvedSuccessActive
snap.PulseIntelligenceExternalAgentResolvedOperationsLoop30d =
verifiedFindingResolutionActive &&
externalAgentCollaborationActive
snap.PulseIntelligenceMCPAdapterOperationsLoop30d =
patrolIssueEvidenceActive &&
mcpAdapterCollaborationActive &&
governedActionDecisionActive
snap.PulseIntelligenceMCPAdapterApprovedExecutionLoop30d =
patrolIssueEvidenceActive &&
mcpAdapterCollaborationActive &&
approvedExecutionActive
snap.PulseIntelligenceMCPAdapterApprovedActionSuccessLoop30d =
patrolIssueEvidenceActive &&
mcpAdapterCollaborationActive &&
approvedSuccessActive
snap.PulseIntelligenceMCPAdapterResolvedOperationsLoop30d =
verifiedFindingResolutionActive &&
mcpAdapterCollaborationActive
snap.PulseIntelligenceLoopActive30d =
snap.PulseIntelligenceAssistantAICalls30d > 0 ||
snap.PulseIntelligenceOperationsLoopStarterRequests30d > 0 ||
patrolActive ||
pulseIntelligenceExternalAgentActivity(snap) ||
snap.PulseIntelligenceGovernedActionActive30d
}
func pulseIntelligenceExternalAgentActivity(snap *telemetry.Snapshot) bool {
if snap == nil {
return false
}
evidence := telemetry.PulseIntelligenceExternalAgentEvidence{
Used: snap.PulseIntelligenceExternalAgentUsed30d,
MCPAdapterUsed: snap.PulseIntelligenceMCPAdapterUsed30d,
ContextRequests: snap.PulseIntelligenceExternalAgentContextRequests30d,
EventStreamRequests: snap.PulseIntelligenceExternalAgentEventStreamRequests30d,
ProvisioningRequests: snap.PulseIntelligenceExternalAgentProvisioningRequests30d,
OperatorStateRequests: snap.PulseIntelligenceExternalAgentOperatorStateRequests30d,
FindingRequests: snap.PulseIntelligenceExternalAgentFindingRequests30d,
ActionRequests: snap.PulseIntelligenceExternalAgentActionRequests30d,
}
return evidence.CollaborationActive()
}
func applyPulseIntelligenceAIUsageSnapshot(snap *telemetry.Snapshot, persistence *config.ConfigPersistence, since time.Time) {
if snap == nil || persistence == nil {
return
}
history, err := persistence.LoadAIUsageHistory()
if err != nil || history == nil {
return
}
evidence := telemetry.PulseIntelligenceAIUsageEvidenceFromHistory(history, since)
snap.PulseIntelligenceAssistantAICalls30d = evidence.AssistantAICalls
snap.PulseIntelligenceAssistantContextAICalls30d = evidence.AssistantContextAICalls
snap.PulseIntelligenceAssistantToolCalls30d = evidence.AssistantToolCalls
snap.PulseIntelligencePatrolAICalls30d = evidence.PatrolAICalls
// Exact token totals stay local; only the closed bucket is exported.
snap.PulseIntelligencePatrolInputTokensBucket30d = telemetry.PatrolInputTokensBucket(evidence.PatrolInputTokens)
snap.PulseIntelligencePatrolOutputTokensBucket30d = telemetry.PatrolOutputTokensBucket(evidence.PatrolOutputTokens)
}
func applyPulseIntelligencePatrolRunSnapshot(snap *telemetry.Snapshot, persistence *config.ConfigPersistence, since time.Time) {
if snap == nil || persistence == nil {
return
}
history, err := persistence.LoadPatrolRunHistory()
if err != nil || history == nil {
return
}
// Run and finding volumes come from daily tallies. Counting history.Runs
// directly saturates at the operator-facing history cap, which on an install
// patrolling on a normal schedule is reached within hours of a thirty-day
// window rather than at its end.
snap.PulseIntelligencePatrolRuns30d += history.PatrolRunsSince(since)
snap.PulseIntelligencePatrolNewFindings30d += history.PatrolNewFindingsSince(since)
}
func applyPulseIntelligenceFindingSnapshot(snap *telemetry.Snapshot, persistence *config.ConfigPersistence, since time.Time) {
if snap == nil || persistence == nil {
return
}
data, err := persistence.LoadAIFindings()
if err != nil || data == nil {
return
}
var outcomes telemetry.PatrolInvestigationOutcomeCounts
for _, finding := range data.Findings {
if pulseIntelligenceFindingInvestigatedSince(finding, since) {
snap.PulseIntelligencePatrolInvestigations30d++
// Partition the same population by its current investigation
// state so the outcome buckets always sum to the investigation
// count. Only the closed bucket leaves the install.
outcomes.Add(finding.InvestigationOutcome, finding.InvestigationStatus)
}
if pulseIntelligenceFindingResolvedSince(finding, since) {
snap.PulseIntelligencePatrolResolvedFindings30d++
}
}
snap.PulseIntelligencePatrolInvestigationOutcomeFixVerified30d = outcomes.FixVerified
snap.PulseIntelligencePatrolInvestigationOutcomeFixQueued30d = outcomes.FixQueued
snap.PulseIntelligencePatrolInvestigationOutcomeFixExecuted30d = outcomes.FixExecuted
snap.PulseIntelligencePatrolInvestigationOutcomeFixRejected30d = outcomes.FixRejected
snap.PulseIntelligencePatrolInvestigationOutcomeFixFailed30d = outcomes.FixFailed
snap.PulseIntelligencePatrolInvestigationOutcomeFixVerificationUnknown30d = outcomes.FixVerificationUnknown
snap.PulseIntelligencePatrolInvestigationOutcomeResolved30d = outcomes.Resolved
snap.PulseIntelligencePatrolInvestigationOutcomeNeedsAttention30d = outcomes.NeedsAttention
snap.PulseIntelligencePatrolInvestigationOutcomeCannotFix30d = outcomes.CannotFix
snap.PulseIntelligencePatrolInvestigationOutcomeTimedOut30d = outcomes.TimedOut
snap.PulseIntelligencePatrolInvestigationOutcomeInProgress30d = outcomes.InProgress
snap.PulseIntelligencePatrolInvestigationOutcomeFailed30d = outcomes.Failed
snap.PulseIntelligencePatrolInvestigationOutcomeOther30d = outcomes.Other
}
func pulseIntelligenceFindingInvestigatedSince(finding *config.AIFindingRecord, since time.Time) bool {
if finding == nil {
return false
}
if finding.LastInvestigatedAt != nil && !finding.LastInvestigatedAt.IsZero() && !finding.LastInvestigatedAt.Before(since) {
return true
}
record := finding.InvestigationRecord
if record == nil {
return false
}
if !record.StartedAt.IsZero() && !record.StartedAt.Before(since) {
return true
}
return record.CompletedAt != nil && !record.CompletedAt.IsZero() && !record.CompletedAt.Before(since)
}
func pulseIntelligenceFindingResolvedSince(finding *config.AIFindingRecord, since time.Time) bool {
if finding == nil {
return false
}
if finding.ResolvedAt != nil && !finding.ResolvedAt.IsZero() && !finding.ResolvedAt.Before(since) {
return true
}
switch strings.TrimSpace(finding.InvestigationOutcome) {
case "resolved", "fix_verified":
default:
return false
}
if finding.LastInvestigatedAt != nil && !finding.LastInvestigatedAt.IsZero() && !finding.LastInvestigatedAt.Before(since) {
return true
}
record := finding.InvestigationRecord
return record != nil && record.CompletedAt != nil && !record.CompletedAt.IsZero() && !record.CompletedAt.Before(since)
}
func applyPulseIntelligenceExternalAgentSnapshot(
snap *telemetry.Snapshot,
persistence *config.ConfigPersistence,
currentCfg *config.Config,
since time.Time,
now time.Time,
) {
if snap == nil {
return
}
if currentCfg != nil {
surfaceScopes := pulseIntelligenceExternalAgentSurfaceScopes(agentcapabilities.CanonicalManifest())
if len(surfaceScopes) > 0 {
for _, token := range currentCfg.APITokens {
if pulseIntelligenceExternalAgentToken(token, surfaceScopes, now) {
snap.PulseIntelligenceExternalAgentEnabled = true
}
if pulseIntelligenceExternalAgentOperationsLoopToken(token, surfaceScopes, now) {
snap.PulseIntelligenceExternalAgentOperationsLoopReady = true
}
if snap.PulseIntelligenceExternalAgentEnabled && snap.PulseIntelligenceExternalAgentOperationsLoopReady {
break
}
}
}
}
if persistence == nil {
return
}
history, err := persistence.LoadExternalAgentActivityHistory()
if err != nil || history == nil {
return
}
evidence := telemetry.PulseIntelligenceExternalAgentEvidenceFromHistory(history, since)
snap.PulseIntelligenceExternalAgentUsed30d = evidence.Used
snap.PulseIntelligenceMCPAdapterUsed30d = evidence.MCPAdapterUsed
snap.PulseIntelligenceExternalAgentContextRequests30d = evidence.ContextRequests
snap.PulseIntelligenceExternalAgentEventStreamRequests30d = evidence.EventStreamRequests
snap.PulseIntelligenceExternalAgentProvisioningRequests30d = evidence.ProvisioningRequests
snap.PulseIntelligenceExternalAgentOperatorStateRequests30d = evidence.OperatorStateRequests
snap.PulseIntelligenceExternalAgentFindingRequests30d = evidence.FindingRequests
snap.PulseIntelligenceExternalAgentActionRequests30d = evidence.ActionRequests
}
func applyPulseIntelligenceWorkflowPromptSnapshot(snap *telemetry.Snapshot, persistence *config.ConfigPersistence, since time.Time) {
if snap == nil || persistence == nil {
return
}
history, err := persistence.LoadWorkflowPromptActivityHistory()
if err != nil || history == nil {
return
}
for _, event := range history.Events {
if event.Timestamp.IsZero() || event.Timestamp.Before(since) {
continue
}
if strings.TrimSpace(event.PromptName) != agentcapabilities.PulseWorkflowPromptOperationsLoop {
continue
}
snap.PulseIntelligenceOperationsLoopStarterRequests30d++
switch strings.TrimSpace(event.Surface) {
case config.WorkflowPromptActivitySurfacePulseAssistant:
snap.PulseIntelligenceAssistantOperationsLoopStarterRequests30d++
case config.WorkflowPromptActivitySurfacePulsePatrol:
snap.PulseIntelligencePatrolOperationsLoopStarterRequests30d++
snap.PulseIntelligencePatrolControlOperationsLoopStarterRequests30d++
case config.WorkflowPromptActivitySurfacePatrolControl:
snap.PulseIntelligencePatrolControlOperationsLoopStarterRequests30d++
case config.WorkflowPromptActivitySurfacePatrolAutonomy:
snap.PulseIntelligencePatrolControlOperationsLoopStarterRequests30d++
// This telemetry field is the compatibility aggregate for paid Patrol autonomy handoffs.
snap.PulseIntelligenceProActivationOperationsLoopStarterRequests30d++
case config.WorkflowPromptActivitySurfaceProActivation:
snap.PulseIntelligencePatrolControlOperationsLoopStarterRequests30d++
snap.PulseIntelligenceProActivationOperationsLoopStarterRequests30d++
case config.WorkflowPromptActivitySurfacePulseMCP:
snap.PulseIntelligenceMCPOperationsLoopStarterRequests30d++
}
}
}
func pulseIntelligenceExternalAgentSurfaceScopes(manifest agentcapabilities.Manifest) []string {
return agentcapabilities.RequiredCapabilityScopes(
agentcapabilities.ManifestSurfaceToolCapabilities(manifest, agentcapabilities.SurfaceIDPulseMCP),
)
}
func pulseIntelligenceExternalAgentToken(token config.APITokenRecord, surfaceScopes []string, now time.Time) bool {
if token.ExpiresAt != nil && now.After(token.ExpiresAt.UTC()) {
return false
}
token = token.Clone()
for _, scope := range surfaceScopes {
if strings.TrimSpace(scope) == "" {
continue
}
if token.HasScope(scope) {
return true
}
}
return false
}
func pulseIntelligenceExternalAgentOperationsLoopToken(token config.APITokenRecord, surfaceScopes []string, now time.Time) bool {
if token.ExpiresAt != nil && now.After(token.ExpiresAt.UTC()) {
return false
}
token = token.Clone()
coversLoop := false
for _, scope := range surfaceScopes {
scope = strings.TrimSpace(scope)
if scope == "" {
continue
}
coversLoop = true
if !token.HasScope(scope) {
return false
}
}
return coversLoop
}