package qualification import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "sort" "strings" "sync" "time" "github.com/rcourtman/pulse-go-rewrite/internal/ai/providers" "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources" "github.com/rcourtman/pulse-go-rewrite/pkg/aicontracts" ) const subscriptionAgentPreflightEvidenceGrace = 15 * time.Second type ClientConfig struct { BaseURL string Username string Password string Timeout time.Duration } type PulseClient struct { config ClientConfig client *http.Client } func NewPulseClient(config ClientConfig) (*PulseClient, error) { config.BaseURL = strings.TrimRight(strings.TrimSpace(config.BaseURL), "/") if config.BaseURL == "" { return nil, errors.New("Pulse base URL is required") } parsed, err := url.Parse(config.BaseURL) if err != nil || parsed.Scheme == "" || parsed.Host == "" { return nil, fmt.Errorf("invalid Pulse base URL %q", config.BaseURL) } if parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" { return nil, errors.New("Pulse base URL must not contain credentials, query parameters, or a fragment") } if config.Timeout <= 0 { config.Timeout = 10 * time.Minute } return &PulseClient{config: config, client: &http.Client{Timeout: config.Timeout}}, nil } type AISettings struct { Enabled bool `json:"enabled"` Model string `json:"model"` PatrolModel string `json:"patrol_model"` PatrolEnabled bool `json:"patrol_enabled"` CodexSubscriptionEnabled bool `json:"codex_subscription_enabled"` ClaudeSubscriptionEnabled bool `json:"claude_subscription_enabled"` ZaiBaseURL string `json:"zai_base_url"` PatrolPreflight *PatrolPreflightSnapshot `json:"patrol_preflight,omitempty"` } type PatrolPreflightSnapshot struct { Success bool `json:"success"` Provider string `json:"provider"` Model string `json:"model"` ToolCallObserved bool `json:"tool_call_observed"` DurationMs int64 `json:"duration_ms"` Cause string `json:"cause"` Title string `json:"title"` Summary string `json:"summary"` Recommendation string `json:"recommendation"` RecordedAt string `json:"recorded_at"` RecordedAtUnix int64 `json:"recorded_at_unix"` } type PatrolPreflightResult struct { Success bool `json:"success"` Provider string `json:"provider"` Model string `json:"model"` ToolCallObserved bool `json:"tool_call_observed"` DurationMs int64 `json:"duration_ms"` Message string `json:"message"` Cause string `json:"cause"` Summary string `json:"summary"` Recommendation string `json:"recommendation"` } // PatrolModelSuiteLease pins one configured Patrol route for a complete live // qualification suite. Individual scenario runners only observe and assert // this route; they never rewrite settings or retrigger provider preflight. type PatrolModelSuiteLease struct { Model string Preflight PatrolPreflightResult restore func(context.Context) error closeOnce sync.Once closeErr error } func (l *PatrolModelSuiteLease) Close(ctx context.Context) error { if l == nil { return nil } l.closeOnce.Do(func() { if l.restore != nil { l.closeErr = l.restore(ctx) } }) return l.closeErr } type PulseVersion struct { Version string `json:"version"` Build string `json:"build"` Runtime string `json:"runtime"` IsDevelopment bool `json:"isDevelopment"` } func (s AISettings) EffectivePatrolModel() string { if value := strings.TrimSpace(s.PatrolModel); value != "" { return value } return strings.TrimSpace(s.Model) } func patrolModelSuiteAcquisitionTimeout(provider string, requested time.Duration) time.Duration { if provider != string(providers.SubscriptionAgentCodex) && provider != string(providers.SubscriptionAgentClaude) { return requested } minimum := providers.SubscriptionAgentMinimumRequestTimeout + subscriptionAgentPreflightEvidenceGrace if requested < minimum { return minimum } return requested } type PatrolReadiness struct { Status string `json:"status"` Ready bool `json:"ready"` Summary string `json:"summary"` Provider string `json:"provider"` Model string `json:"model"` } type PatrolStatus struct { RuntimeState string `json:"runtime_state"` Running bool `json:"running"` Enabled bool `json:"enabled"` Healthy bool `json:"healthy"` Readiness PatrolReadiness `json:"readiness"` } type PatrolAutonomy struct { AutonomyLevel string `json:"autonomy_level"` EffectiveAutonomyLevel string `json:"effective_autonomy_level"` } func (a PatrolAutonomy) Effective() string { if value := strings.TrimSpace(a.EffectiveAutonomyLevel); value != "" { return value } return strings.TrimSpace(a.AutonomyLevel) } type Resource struct { ID string `json:"id"` Type string `json:"type"` Technology string `json:"technology"` Name string `json:"name"` Status string `json:"status"` LastSeen any `json:"lastSeen,omitempty"` Labels map[string]any `json:"labels,omitempty"` Docker *DockerResource `json:"docker,omitempty"` } type DockerResource struct { ContainerState string `json:"containerState,omitempty"` Health string `json:"health,omitempty"` RestartCount int `json:"restartCount,omitempty"` } type Finding struct { ID string `json:"id"` Key string `json:"key"` Severity string `json:"severity"` Category string `json:"category"` ResourceID string `json:"resource_id"` ResourceName string `json:"resource_name"` ResourceType string `json:"resource_type"` Title string `json:"title"` Description string `json:"description"` Impact string `json:"impact,omitempty"` Recommendation string `json:"recommendation"` Evidence string `json:"evidence"` Source string `json:"source,omitempty"` DetectedAt time.Time `json:"detected_at"` LastSeenAt time.Time `json:"last_seen_at"` ResolvedAt *time.Time `json:"resolved_at,omitempty"` } type ToolCall struct { ID string `json:"id"` ToolName string `json:"tool_name"` Input string `json:"input"` Output string `json:"output"` Success bool `json:"success"` StartTime int64 `json:"start_time"` EndTime int64 `json:"end_time"` Duration int64 `json:"duration_ms"` } type PatrolFindingAssessment struct { FindingID string `json:"finding_id"` Verdict string `json:"verdict"` Evidence string `json:"evidence"` Reason string `json:"reason"` AssessedAt time.Time `json:"assessed_at"` } type PatrolRun struct { ID string `json:"id"` StartedAt time.Time `json:"started_at"` CompletedAt time.Time `json:"completed_at"` DurationMs int64 `json:"duration_ms"` Type string `json:"type"` TriggerReason string `json:"trigger_reason"` ScopeResourceIDs []string `json:"scope_resource_ids,omitempty"` EffectiveScopeResourceIDs []string `json:"effective_scope_resource_ids,omitempty"` ResourcesChecked int `json:"resources_checked"` NewFindings int `json:"new_findings"` ExistingFindings int `json:"existing_findings"` RejectedFindings int `json:"rejected_findings"` ResolvedFindings int `json:"resolved_findings"` FindingsSummary string `json:"findings_summary"` FindingIDs []string `json:"finding_ids"` FindingAssessments []PatrolFindingAssessment `json:"finding_assessments,omitempty"` ErrorCount int `json:"error_count"` Status string `json:"status"` ErrorSummary string `json:"error_summary,omitempty"` AIAnalysis string `json:"ai_analysis,omitempty"` InputTokens int `json:"input_tokens"` OutputTokens int `json:"output_tokens"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCallCount int `json:"tool_call_count"` } func (c *PulseClient) request(ctx context.Context, method, path string, input, output any) error { var body io.Reader if input != nil { encoded, err := json.Marshal(input) if err != nil { return err } body = bytes.NewReader(encoded) } req, err := http.NewRequestWithContext(ctx, method, c.config.BaseURL+path, body) if err != nil { return err } if input != nil { req.Header.Set("Content-Type", "application/json") } req.Header.Set("Accept", "application/json") req.SetBasicAuth(c.config.Username, c.config.Password) resp, err := c.client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { payload, _ := io.ReadAll(io.LimitReader(resp.Body, 32*1024)) return &HTTPError{StatusCode: resp.StatusCode, Path: path, Body: sanitizeArtifactText(string(payload))} } if output == nil || resp.StatusCode == http.StatusNoContent { return nil } if err := json.NewDecoder(resp.Body).Decode(output); err != nil { return fmt.Errorf("decode %s: %w", path, err) } return nil } type HTTPError struct { StatusCode int Path string Body string } func (e *HTTPError) Error() string { return fmt.Sprintf("Pulse API %s returned %d: %s", e.Path, e.StatusCode, e.Body) } func (c *PulseClient) Settings(ctx context.Context) (AISettings, error) { var settings AISettings err := c.request(ctx, http.MethodGet, "/api/settings/ai", nil, &settings) return settings, err } func (c *PulseClient) Version(ctx context.Context) (PulseVersion, error) { var version PulseVersion err := c.request(ctx, http.MethodGet, "/api/version", nil, &version) return version, err } func (c *PulseClient) Status(ctx context.Context) (PatrolStatus, error) { var status PatrolStatus err := c.request(ctx, http.MethodGet, "/api/ai/patrol/status", nil, &status) return status, err } func (c *PulseClient) Autonomy(ctx context.Context) (PatrolAutonomy, error) { var autonomy PatrolAutonomy err := c.request(ctx, http.MethodGet, "/api/ai/patrol/autonomy", nil, &autonomy) return autonomy, err } func (c *PulseClient) SetPatrolModel(ctx context.Context, model string) error { return c.request(ctx, http.MethodPut, "/api/settings/ai/update", map[string]any{"patrol_model": strings.TrimSpace(model)}, nil) } func (c *PulseClient) OverridePatrolModel(ctx context.Context, model string) (func(context.Context) error, error) { model = strings.TrimSpace(model) if model == "" { return func(context.Context) error { return nil }, nil } settings, err := c.Settings(ctx) if err != nil { return nil, err } previous := settings.PatrolModel provider, _ := splitModel(model) payload := make(map[string]any) restorePayload := make(map[string]any) if previous != model { payload["patrol_model"] = model restorePayload["patrol_model"] = previous } if provider == "codex-subscription" && !settings.CodexSubscriptionEnabled { payload["codex_subscription_enabled"] = true restorePayload["codex_subscription_enabled"] = settings.CodexSubscriptionEnabled } if provider == "claude-subscription" && !settings.ClaudeSubscriptionEnabled { payload["claude_subscription_enabled"] = true restorePayload["claude_subscription_enabled"] = settings.ClaudeSubscriptionEnabled } if len(payload) == 0 { return func(context.Context) error { return nil }, nil } if err := c.request(ctx, http.MethodPut, "/api/settings/ai/update", payload, nil); err != nil { return nil, err } return func(restoreCtx context.Context) error { return c.request(restoreCtx, http.MethodPut, "/api/settings/ai/update", restorePayload, nil) }, nil } // AcquirePatrolModelSuite pins model for an entire live suite and obtains one // fresh tool-call preflight result before any scenario is provisioned. A model // change already triggers Pulse's asynchronous preflight, so acquisition waits // for that exact new cache record instead of issuing a duplicate provider call. // When the requested route is already configured, acquisition performs one // synchronous preflight to ensure the suite never relies on stale cache state. func (c *PulseClient) AcquirePatrolModelSuite(ctx context.Context, model string, timeout, poll time.Duration) (*PatrolModelSuiteLease, error) { if timeout <= 0 { timeout = 45 * time.Second } if poll <= 0 { poll = 250 * time.Millisecond } before, err := c.Settings(ctx) if err != nil { return nil, err } target := strings.TrimSpace(model) if target == "" { target = before.EffectivePatrolModel() } provider, bareModel := splitModel(target) if provider == "" || bareModel == "" { return nil, fmt.Errorf("Patrol qualification requires a concrete provider:model route, got %q", target) } timeout = patrolModelSuiteAcquisitionTimeout(provider, timeout) routeChanged := before.EffectivePatrolModel() != target || (provider == "codex-subscription" && !before.CodexSubscriptionEnabled) || (provider == "claude-subscription" && !before.ClaudeSubscriptionEnabled) previousPreflightUnix := int64(0) previousPreflightWasTarget := false if before.PatrolPreflight != nil { previousPreflightUnix = before.PatrolPreflight.RecordedAtUnix previousPreflightWasTarget = strings.EqualFold(before.PatrolPreflight.Provider, provider) && before.PatrolPreflight.Model == bareModel } if routeChanged && previousPreflightWasTarget { freshnessDeadline := time.Now().Add(timeout) for time.Now().Unix() <= previousPreflightUnix { if time.Now().After(freshnessDeadline) { return nil, fmt.Errorf("Patrol qualification could not establish a fresh preflight clock boundary after %s", time.Unix(previousPreflightUnix, 0).UTC().Format(time.RFC3339)) } select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(50 * time.Millisecond): } } } restore, err := c.OverridePatrolModel(ctx, model) if err != nil { return nil, err } restoreOnError := func(acquireErr error) (*PatrolModelSuiteLease, error) { restoreCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() return nil, errors.Join(acquireErr, restore(restoreCtx)) } var preflight PatrolPreflightResult if routeChanged { preflight, err = c.waitForPatrolPreflight(ctx, provider, bareModel, previousPreflightUnix, previousPreflightWasTarget, timeout, poll) } else { preflight, err = c.RunPatrolPreflight(ctx) } if err != nil { return restoreOnError(err) } if err := validatePatrolPreflight(target, preflight); err != nil { return restoreOnError(err) } settings, err := c.Settings(ctx) if err != nil { return restoreOnError(err) } status, err := c.Status(ctx) if err != nil { return restoreOnError(err) } if err := validatePatrolRoute(target, settings, status); err != nil { return restoreOnError(err) } return &PatrolModelSuiteLease{Model: target, Preflight: preflight, restore: restore}, nil } func (c *PulseClient) RunPatrolPreflight(ctx context.Context) (PatrolPreflightResult, error) { var result PatrolPreflightResult err := c.request(ctx, http.MethodPost, "/api/ai/patrol/preflight", map[string]any{}, &result) return result, err } func (c *PulseClient) waitForPatrolPreflight(ctx context.Context, provider, model string, afterUnix int64, requireNewer bool, timeout, poll time.Duration) (PatrolPreflightResult, error) { deadline := time.Now().Add(timeout) for { settings, err := c.Settings(ctx) if err == nil && settings.PatrolPreflight != nil { snapshot := settings.PatrolPreflight fresh := !requireNewer || snapshot.RecordedAtUnix > afterUnix if fresh && strings.EqualFold(snapshot.Provider, provider) && snapshot.Model == model { return PatrolPreflightResult{ Success: snapshot.Success, Provider: snapshot.Provider, Model: snapshot.Model, ToolCallObserved: snapshot.ToolCallObserved, DurationMs: snapshot.DurationMs, Message: snapshot.Summary, Cause: snapshot.Cause, Summary: snapshot.Title, Recommendation: snapshot.Recommendation, }, nil } } if time.Now().After(deadline) { return PatrolPreflightResult{}, fmt.Errorf("Patrol qualification preflight did not publish fresh evidence for %s:%s before %s", provider, model, deadline.UTC().Format(time.RFC3339)) } select { case <-ctx.Done(): return PatrolPreflightResult{}, ctx.Err() case <-time.After(poll): } } } func validatePatrolPreflight(target string, result PatrolPreflightResult) error { provider, model := splitModel(target) if !strings.EqualFold(result.Provider, provider) || result.Model != model { return fmt.Errorf("Patrol qualification preflight resolved %s:%s, expected %s", result.Provider, result.Model, target) } if result.Success && result.ToolCallObserved { return nil } summary := strings.TrimSpace(result.Message) if summary == "" { summary = strings.TrimSpace(result.Summary) } if summary == "" { summary = "provider did not complete the tool-call preflight" } if cause := strings.TrimSpace(result.Cause); cause != "" { return fmt.Errorf("Patrol qualification preflight failed for %s (%s): %s", target, cause, summary) } return fmt.Errorf("Patrol qualification preflight failed for %s: %s", target, summary) } func validatePatrolRoute(expected string, settings AISettings, status PatrolStatus) error { expected = strings.TrimSpace(expected) if actual := settings.EffectivePatrolModel(); actual != expected { return fmt.Errorf("configured Patrol model %q does not match qualification suite route %q", actual, expected) } provider, model := splitModel(expected) if !strings.EqualFold(status.Readiness.Provider, provider) || status.Readiness.Model != expected && status.Readiness.Model != model { return fmt.Errorf("Patrol readiness resolved %s:%s, expected %s", status.Readiness.Provider, status.Readiness.Model, expected) } if !status.Readiness.Ready || strings.EqualFold(status.Readiness.Provider, "demo") { return fmt.Errorf("Patrol qualification suite route is not ready: %s", status.Readiness.Summary) } return nil } func (c *PulseClient) Resources(ctx context.Context) ([]Resource, error) { var response struct { Data []Resource `json:"data"` } err := c.request(ctx, http.MethodGet, "/api/resources?limit=1000", nil, &response) return response.Data, err } func (c *PulseClient) WaitForResources(ctx context.Context, names map[string]string, timeout, poll time.Duration) (map[string]Resource, error) { return c.WaitForResourcesMatching(ctx, names, timeout, poll, nil) } func (c *PulseClient) WaitForResourcesMatching(ctx context.Context, names map[string]string, timeout, poll time.Duration, validate func(map[string]Resource) error) (map[string]Resource, error) { if timeout <= 0 { timeout = 5 * time.Minute } if poll <= 0 { poll = 5 * time.Second } deadline := time.Now().Add(timeout) var lastValidationErr error for { resources, err := c.Resources(ctx) if err == nil { matched := make(map[string]Resource, len(names)) for alias, name := range names { for _, resource := range resources { if resource.Name == name { matched[alias] = resource break } } } if len(matched) == len(names) { if validate == nil { return matched, nil } if validationErr := validate(matched); validationErr == nil { return matched, nil } else { lastValidationErr = validationErr } } } if time.Now().After(deadline) { if lastValidationErr != nil { return nil, fmt.Errorf("collection exposed exact resources but did not converge to scenario-owned fault state before %s: %w", deadline.UTC().Format(time.RFC3339), lastValidationErr) } return nil, fmt.Errorf("collection did not expose all %d exact resource names before %s", len(names), deadline.UTC().Format(time.RFC3339)) } select { case <-ctx.Done(): return nil, ctx.Err() case <-time.After(poll): } } } func (c *PulseClient) Findings(ctx context.Context) ([]Finding, error) { var findings []Finding err := c.request(ctx, http.MethodGet, "/api/ai/patrol/findings", nil, &findings) return findings, err } func (c *PulseClient) Runs(ctx context.Context) ([]PatrolRun, error) { var runs []PatrolRun err := c.request(ctx, http.MethodGet, "/api/ai/patrol/runs?limit=100&include=tool_calls", nil, &runs) return runs, err } func (c *PulseClient) Run(ctx context.Context, runID string) (PatrolRun, error) { var run PatrolRun err := c.request(ctx, http.MethodGet, "/api/ai/patrol/runs/"+url.PathEscape(runID)+"?include=tool_calls", nil, &run) return run, err } func (c *PulseClient) Trigger(ctx context.Context, resourceIDs []string, _ string) error { var body any if len(resourceIDs) > 0 { body = map[string]any{"resource_ids": resourceIDs} } return c.request(ctx, http.MethodPost, "/api/ai/patrol/run", body, nil) } func (c *PulseClient) TriggerAndWait(ctx context.Context, resourceIDs []string, contextText string, timeout time.Duration) (PatrolRun, error) { before, err := c.Runs(ctx) if err != nil { return PatrolRun{}, err } known := make(map[string]struct{}, len(before)) for _, run := range before { known[run.ID] = struct{}{} } triggeredAt := time.Now().UTC() if err := c.Trigger(ctx, resourceIDs, contextText); err != nil { return PatrolRun{}, err } if timeout <= 0 { timeout = 10 * time.Minute } deadline := time.Now().Add(timeout) for { runs, runErr := c.Runs(ctx) if runErr == nil { sort.Slice(runs, func(i, j int) bool { return runs[i].StartedAt.After(runs[j].StartedAt) }) for _, candidate := range runs { if _, exists := known[candidate.ID]; exists || candidate.CompletedAt.IsZero() { continue } if candidate.StartedAt.Before(triggeredAt.Add(-2 * time.Second)) { continue } if len(resourceIDs) > 0 && !intersects(candidate.EffectiveScopeResourceIDs, resourceIDs) && !intersects(candidate.ScopeResourceIDs, resourceIDs) { continue } return c.Run(ctx, candidate.ID) } } if time.Now().After(deadline) { return PatrolRun{}, fmt.Errorf("no completed Patrol run associated with trigger at %s", triggeredAt.Format(time.RFC3339Nano)) } select { case <-ctx.Done(): return PatrolRun{}, ctx.Err() case <-time.After(2 * time.Second): } } } func (c *PulseClient) Investigation(ctx context.Context, findingID string) (aicontracts.InvestigationSession, error) { var result aicontracts.InvestigationSession err := c.request(ctx, http.MethodGet, "/api/ai/findings/"+url.PathEscape(findingID)+"/investigation", nil, &result) return result, err } func (c *PulseClient) WaitForInvestigation(ctx context.Context, findingID string, timeout time.Duration) (aicontracts.InvestigationSession, error) { if timeout <= 0 { timeout = 10 * time.Minute } deadline := time.Now().Add(timeout) for { investigation, err := c.Investigation(ctx, findingID) if err == nil { status := strings.ToLower(strings.TrimSpace(string(investigation.Status))) if status != "" && status != "pending" && status != "running" { return investigation, nil } } else { var apiErr *HTTPError if !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusNotFound { return aicontracts.InvestigationSession{}, err } } if time.Now().After(deadline) { return aicontracts.InvestigationSession{}, fmt.Errorf("investigation for finding %s did not complete", findingID) } select { case <-ctx.Done(): return aicontracts.InvestigationSession{}, ctx.Err() case <-time.After(2 * time.Second): } } } type ActionProjection struct { unifiedresources.ActionAuditRecord Resource *struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` } `json:"resource,omitempty"` } type ActionDetail struct { Audit ActionProjection `json:"audit"` Events []unifiedresources.ActionLifecycleEvent `json:"events"` ReadOnly bool `json:"readOnly"` } func (c *PulseClient) Actions(ctx context.Context, view string) ([]ActionProjection, error) { if view == "" { view = "pending" } var response struct { Actions []ActionProjection `json:"actions"` } err := c.request(ctx, http.MethodGet, "/api/actions?view="+url.QueryEscape(view)+"&limit=500", nil, &response) return response.Actions, err } func (c *PulseClient) Action(ctx context.Context, actionID string) (ActionDetail, error) { var detail ActionDetail err := c.request(ctx, http.MethodGet, "/api/actions/"+url.PathEscape(actionID), nil, &detail) return detail, err } func (c *PulseClient) DecideAction(ctx context.Context, actionID, outcome, reason, planHash string) (ActionProjection, error) { var response struct { Audit ActionProjection `json:"audit"` } err := c.request(ctx, http.MethodPost, "/api/actions/"+url.PathEscape(actionID)+"/decision", map[string]any{ "outcome": outcome, "reason": reason, "planHash": planHash, }, &response) return response.Audit, err } func (c *PulseClient) ExecuteAction(ctx context.Context, actionID, reason, planHash string) (ActionProjection, error) { var response struct { Audit ActionProjection `json:"audit"` } err := c.request(ctx, http.MethodPost, "/api/actions/"+url.PathEscape(actionID)+"/execute", map[string]any{ "reason": reason, "planHash": planHash, }, &response) return response.Audit, err } func (c *PulseClient) WaitForAction(ctx context.Context, actionID string, timeout time.Duration) (ActionDetail, error) { if timeout <= 0 { timeout = 5 * time.Minute } deadline := time.Now().Add(timeout) for { detail, err := c.Action(ctx, actionID) if err == nil { switch detail.Audit.State { case unifiedresources.ActionStateCompleted, unifiedresources.ActionStateFailed, unifiedresources.ActionStateRejected, unifiedresources.ActionStateExpired: return detail, nil } } else { return ActionDetail{}, err } if time.Now().After(deadline) { return ActionDetail{}, fmt.Errorf("action %s did not reach a terminal state", actionID) } select { case <-ctx.Done(): return ActionDetail{}, ctx.Err() case <-time.After(time.Second): } } } func filterRunFindings(before, after []Finding, run PatrolRun, resourceIDs map[string]Resource, triggeredAt time.Time) []Finding { wanted := make(map[string]struct{}, len(run.FindingIDs)) for _, id := range run.FindingIDs { wanted[id] = struct{}{} } beforeSeen := make(map[string]time.Time, len(before)) for _, finding := range before { beforeSeen[finding.ID] = finding.LastSeenAt } resources := make(map[string]struct{}, len(resourceIDs)) for _, resource := range resourceIDs { resources[resource.ID] = struct{}{} } var result []Finding for _, finding := range after { _, runOwned := wanted[finding.ID] _, resourceOwned := resources[finding.ResourceID] previousSeen, existedBefore := beforeSeen[finding.ID] updated := (existedBefore && finding.LastSeenAt.After(previousSeen)) || (!existedBefore && (finding.LastSeenAt.After(triggeredAt.Add(-2*time.Second)) || finding.DetectedAt.After(triggeredAt.Add(-2*time.Second)))) if runOwned || (resourceOwned && updated) { result = append(result, finding) } } sort.Slice(result, func(i, j int) bool { return result[i].ID < result[j].ID }) return result } func intersects(left, right []string) bool { wanted := make(map[string]struct{}, len(right)) for _, value := range right { wanted[value] = struct{}{} } for _, value := range left { if _, ok := wanted[value]; ok { return true } } return false }