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" ) // applyAIConfigTelemetrySnapshot copies the AI feature flags from the stored // AI config. It never touches DiscoveryEnabled: that flag reports network // subnet discovery only, since AI Discovery was removed. func applyAIConfigTelemetrySnapshot(snap *telemetry.Snapshot, aiCfg *config.AIConfig) { if snap == nil || aiCfg == nil { return } snap.AIEnabled = aiCfg.Enabled // Closed route class only; provider, model, endpoint, and account identity // never enter the snapshot. snap.AIProviderClass = telemetry.ClassifyAIProviderClass(aiCfg) snap.PatrolEnabled = aiCfg.IsPatrolEnabled() snap.AIActionsEnabled = aiCfg.IsControlEnabled() snap.AlertAIEnabled = aiCfg.IsAlertTriggeredAnalysisEnabled() } 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 }