Files
pulse/internal/api/telemetry_pulse_intelligence_test.go
T
rcourtman 63c40ebe5e fix(telemetry): make patrol run volume and refusal causes readable
Two counters could not answer the question they exist for.

pulse_intelligence_patrol_runs_30d was counted from the operator-facing
patrol run history, which is capped at MaxPatrolRunHistory (100). On any
install patrolling on a normal schedule that cap is reached within hours
of a thirty-day window opening, so the field reported the cap rather than
the run count, and the calls-per-run ratio derived from it was inflated by
a censored denominator. Patrol run history now carries an uncapped daily
tally beside the capped list, advanced from a persisted high-water mark so
repeated full-list saves cannot double count, pruned to 31 days, and read
in preference to the list. An install with no tally yet falls back to the
list, so the counter never regresses on upgrade.

Schema v8 split agent-side pre-mutation refusals into target-change,
prerequisite and contract categories so they would stop hiding in "other".
In production all three are zero fleet-wide and "other" still absorbs every
refusal, because the dominant reason code is the legacy preflight_refused
aggregate recorded when an agent sends no machine reason code at all.
Agents older than the typed refusal contract report every refusal that way,
so the split is starved rather than broken, and folding the two together
made those two states indistinguishable. Schema v9 counts uncoded refusals
separately.
2026-08-19 17:00:02 +01:00

515 lines
24 KiB
Go

package api
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/actionlifecycle"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/telemetry"
unifiedresources "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
)
func TestGetPulseIntelligenceActionTelemetry_SummarizesActionGovernanceOnly(t *testing.T) {
now := time.Date(2026, 6, 20, 9, 30, 0, 0, time.UTC)
since := now.Add(-telemetry.PulseIntelligenceTelemetryWindow)
dataDir := t.TempDir()
mtPersistence := config.NewMultiTenantPersistence(dataDir)
if err := mtPersistence.SaveOrganization(&models.Organization{ID: "tenant-a", DisplayName: "Tenant A"}); err != nil {
t.Fatalf("SaveOrganization: %v", err)
}
router := &Router{
multiTenant: mtPersistence,
resourceHandlers: NewResourceHandlers(&config.Config{DataPath: dataDir}),
}
defaultStore, err := router.resourceHandlers.getStore("default")
if err != nil {
t.Fatalf("default getStore: %v", err)
}
tenantStore, err := router.resourceHandlers.getStore("tenant-a")
if err != nil {
t.Fatalf("tenant getStore: %v", err)
}
approvedCompleted := pulseTelemetryActionRecord("approved-completed", now.Add(-35*time.Minute), unifiedresources.ActionStateCompleted, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodUI, Timestamp: now.Add(-34 * time.Minute), Actor: "operator"},
})
approvedCompleted.Result = &unifiedresources.ExecutionResult{Success: true}
approvedCompleted.VerificationOutcome = unifiedresources.VerificationOutcome{Status: unifiedresources.VerificationVerified}
records := map[unifiedresources.ResourceStore][]unifiedresources.ActionAuditRecord{
defaultStore: {
pulseTelemetryActionRecord("plan-only", now.Add(-time.Hour), unifiedresources.ActionStatePlanned, false, nil),
pulseTelemetryActionRecord("pending", now.Add(-50*time.Minute), unifiedresources.ActionStatePending, true, nil),
pulseTelemetryActionRecord("approved-executing", now.Add(-40*time.Minute), unifiedresources.ActionStateExecuting, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodUI, Timestamp: now.Add(-39 * time.Minute), Actor: "operator"},
}),
approvedCompleted,
pulseTelemetryActionRecord("old-approved", since.Add(-time.Minute), unifiedresources.ActionStateCompleted, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodUI, Timestamp: since.Add(-2 * time.Minute), Actor: "operator"},
}),
},
tenantStore: {
pulseTelemetryActionRecord("tenant-approved-failed", now.Add(-30*time.Minute), unifiedresources.ActionStateFailed, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodAPI, Timestamp: now.Add(-29 * time.Minute), Actor: "operator"},
}),
pulseTelemetryActionRecord("tenant-rejected", now.Add(-20*time.Minute), unifiedresources.ActionStateRejected, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeRejected, Method: unifiedresources.MethodUI, Timestamp: now.Add(-19 * time.Minute), Actor: "operator"},
}),
},
}
for store, storeRecords := range records {
for _, record := range storeRecords {
if err := store.RecordActionAudit(record); err != nil {
t.Fatalf("RecordActionAudit(%s): %v", record.ID, err)
}
}
}
got := router.GetPulseIntelligenceActionTelemetry(since)
if got.ActionPlans30d != 6 {
t.Fatalf("ActionPlans30d = %d, want 6", got.ActionPlans30d)
}
if got.ApprovalRequests30d != 5 {
t.Fatalf("ApprovalRequests30d = %d, want 5", got.ApprovalRequests30d)
}
if got.RejectedActionDecisions30d != 1 {
t.Fatalf("RejectedActionDecisions30d = %d, want 1", got.RejectedActionDecisions30d)
}
if got.ApprovedActionDecisions30d != 3 {
t.Fatalf("ApprovedActionDecisions30d = %d, want 3", got.ApprovedActionDecisions30d)
}
if got.ApprovedActionAttempts30d != 3 {
t.Fatalf("ApprovedActionAttempts30d = %d, want 3", got.ApprovedActionAttempts30d)
}
if got.ApprovedActionSuccesses30d != 1 {
t.Fatalf("ApprovedActionSuccesses30d = %d, want 1", got.ApprovedActionSuccesses30d)
}
}
func TestGetPulseIntelligenceActionTelemetry_CountsApprovedLifecycleAttemptsInsideWindow(t *testing.T) {
now := time.Date(2026, 6, 20, 10, 30, 0, 0, time.UTC)
since := now.Add(-telemetry.PulseIntelligenceTelemetryWindow)
dataDir := t.TempDir()
router := &Router{
resourceHandlers: NewResourceHandlers(&config.Config{DataPath: dataDir}),
}
store, err := router.resourceHandlers.getStore("default")
if err != nil {
t.Fatalf("getStore: %v", err)
}
currentApproved := pulseTelemetryActionRecord("current-approved-executed", now.Add(-2*time.Hour), unifiedresources.ActionStateApproved, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodUI, Timestamp: now.Add(-110 * time.Minute), Actor: "operator"},
})
if err := store.RecordActionAudit(currentApproved); err != nil {
t.Fatalf("RecordActionAudit(currentApproved): %v", err)
}
started, startEvent, err := unifiedresources.BeginActionExecution(currentApproved, "operator", now.Add(-90*time.Minute))
if err != nil {
t.Fatalf("BeginActionExecution(currentApproved): %v", err)
}
if err := store.RecordActionExecutionStart(started, startEvent); err != nil {
t.Fatalf("RecordActionExecutionStart(currentApproved): %v", err)
}
completed, doneEvent, err := unifiedresources.CompleteActionExecution(started, &unifiedresources.ExecutionResult{Success: true, Verification: &unifiedresources.ActionVerificationResult{Ran: true, Success: true, RanAt: now.Add(-80 * time.Minute)}}, "operator", now.Add(-80*time.Minute))
if err != nil {
t.Fatalf("CompleteActionExecution(currentApproved): %v", err)
}
if err := store.RecordActionExecutionResult(completed, doneEvent); err != nil {
t.Fatalf("RecordActionExecutionResult(currentApproved): %v", err)
}
oldCreatedAt := since.Add(-time.Minute)
oldApproved := pulseTelemetryActionRecord("old-approved-executed", oldCreatedAt, unifiedresources.ActionStateApproved, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodAPI, Timestamp: oldCreatedAt.Add(time.Minute), Actor: "operator"},
})
oldApproved.Plan.ExpiresAt = now.Add(time.Hour)
if err := store.RecordActionAudit(oldApproved); err != nil {
t.Fatalf("RecordActionAudit(oldApproved): %v", err)
}
started, startEvent, err = unifiedresources.BeginActionExecution(oldApproved, "operator", now.Add(-60*time.Minute))
if err != nil {
t.Fatalf("BeginActionExecution(oldApproved): %v", err)
}
if err := store.RecordActionExecutionStart(started, startEvent); err != nil {
t.Fatalf("RecordActionExecutionStart(oldApproved): %v", err)
}
oldRefused := pulseTelemetryActionRecord("old-approved-refused", oldCreatedAt, unifiedresources.ActionStateApproved, true, []unifiedresources.ActionApprovalRecord{
{Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodAPI, Timestamp: oldCreatedAt.Add(time.Minute), Actor: "operator"},
})
oldRefused.Plan.ExpiresAt = now.Add(time.Hour)
if err := store.RecordActionAudit(oldRefused); err != nil {
t.Fatalf("RecordActionAudit(oldRefused): %v", err)
}
if _, err := actionlifecycle.RecordRefusedExecution(store, oldRefused, "operator", now.Add(-30*time.Minute), unifiedresources.ErrActionPlanDrift); err != nil {
t.Fatalf("recordRefusedActionExecution(oldRefused): %v", err)
}
unapprovedRefused := pulseTelemetryActionRecord("old-unapproved-refused", oldCreatedAt, unifiedresources.ActionStatePlanned, false, nil)
unapprovedRefused.Plan.ExpiresAt = now.Add(time.Hour)
if err := store.RecordActionAudit(unapprovedRefused); err != nil {
t.Fatalf("RecordActionAudit(unapprovedRefused): %v", err)
}
if _, err := actionlifecycle.RecordRefusedExecution(store, unapprovedRefused, "operator", now.Add(-15*time.Minute), unifiedresources.ErrActionPlanDrift); err != nil {
t.Fatalf("recordRefusedActionExecution(unapprovedRefused): %v", err)
}
oldRejected := pulseTelemetryActionRecord("old-rejected-decision", oldCreatedAt, unifiedresources.ActionStatePending, true, nil)
oldRejected.Plan.ExpiresAt = now.Add(time.Hour)
if err := store.RecordActionAudit(oldRejected); err != nil {
t.Fatalf("RecordActionAudit(oldRejected): %v", err)
}
rejectedAt := now.Add(-10 * time.Minute)
rejected, rejectedEvent, err := unifiedresources.ApplyActionDecision(oldRejected, boundActionTestDecisionApproval(oldRejected.ID, oldRejected.Plan.PlanHash, "operator", unifiedresources.OutcomeRejected, rejectedAt), rejectedAt)
if err != nil {
t.Fatalf("ApplyActionDecision(oldRejected): %v", err)
}
if err := store.RecordActionDecision(rejected, rejectedEvent); err != nil {
t.Fatalf("RecordActionDecision(oldRejected): %v", err)
}
got := router.GetPulseIntelligenceActionTelemetry(since)
if got.ActionPlans30d != 1 {
t.Fatalf("ActionPlans30d = %d, want 1", got.ActionPlans30d)
}
if got.ApprovalRequests30d != 1 {
t.Fatalf("ApprovalRequests30d = %d, want 1", got.ApprovalRequests30d)
}
if got.RejectedActionDecisions30d != 1 {
t.Fatalf("RejectedActionDecisions30d = %d, want 1", got.RejectedActionDecisions30d)
}
if got.ApprovedActionDecisions30d != 1 {
t.Fatalf("ApprovedActionDecisions30d = %d, want 1", got.ApprovedActionDecisions30d)
}
if got.ApprovedActionAttempts30d != 3 {
t.Fatalf("ApprovedActionAttempts30d = %d, want 3", got.ApprovedActionAttempts30d)
}
if got.ApprovedActionSuccesses30d != 1 {
t.Fatalf("ApprovedActionSuccesses30d = %d, want 1", got.ApprovedActionSuccesses30d)
}
}
func TestGetPulseIntelligenceActionTelemetry_RequiresVerifiedOutcomeForApprovedSuccess(t *testing.T) {
now := time.Date(2026, 6, 20, 11, 30, 0, 0, time.UTC)
since := now.Add(-telemetry.PulseIntelligenceTelemetryWindow)
dataDir := t.TempDir()
router := &Router{
resourceHandlers: NewResourceHandlers(&config.Config{DataPath: dataDir}),
}
store, err := router.resourceHandlers.getStore("default")
if err != nil {
t.Fatalf("getStore: %v", err)
}
approved := []unifiedresources.ActionApprovalRecord{{
Outcome: unifiedresources.OutcomeApproved,
Method: unifiedresources.MethodUI,
Timestamp: now.Add(-50 * time.Minute),
Actor: "operator",
}}
successOnly := pulseTelemetryActionRecord("success-only", now.Add(-55*time.Minute), unifiedresources.ActionStateCompleted, true, approved)
successOnly.Result = &unifiedresources.ExecutionResult{Success: true}
outcomeVerified := pulseTelemetryActionRecord("outcome-verified", now.Add(-45*time.Minute), unifiedresources.ActionStateCompleted, true, approved)
outcomeVerified.Result = &unifiedresources.ExecutionResult{Success: true}
outcomeVerified.VerificationOutcome = unifiedresources.VerificationOutcome{Status: unifiedresources.VerificationVerified}
legacyVerification := pulseTelemetryActionRecord("legacy-verification", now.Add(-35*time.Minute), unifiedresources.ActionStateCompleted, true, approved)
legacyVerification.Result = &unifiedresources.ExecutionResult{Success: true}
legacyVerification.Verification = &unifiedresources.ActionVerificationResult{
Ran: true,
Success: true,
RanAt: now.Add(-30 * time.Minute),
}
for _, record := range []unifiedresources.ActionAuditRecord{successOnly, outcomeVerified, legacyVerification} {
if err := store.RecordActionAudit(record); err != nil {
t.Fatalf("RecordActionAudit(%s): %v", record.ID, err)
}
}
got := router.GetPulseIntelligenceActionTelemetry(since)
if got.ApprovedActionSuccesses30d != 2 {
t.Fatalf("ApprovedActionSuccesses30d = %d, want 2 verified outcomes; bare successful execution must not count", got.ApprovedActionSuccesses30d)
}
}
func TestGetPulseIntelligenceActionTelemetry_AttributesApprovedActionFailureCauses(t *testing.T) {
// Cause classification measures staleness against the wall clock, so this
// test anchors records to real time instead of a fixed date.
now := time.Now().UTC()
since := now.Add(-telemetry.PulseIntelligenceTelemetryWindow)
dataDir := t.TempDir()
router := &Router{
resourceHandlers: NewResourceHandlers(&config.Config{DataPath: dataDir}),
}
store, err := router.resourceHandlers.getStore("default")
if err != nil {
t.Fatalf("getStore: %v", err)
}
approvedAt := func(at time.Time) []unifiedresources.ActionApprovalRecord {
return []unifiedresources.ActionApprovalRecord{{
Outcome: unifiedresources.OutcomeApproved,
Method: unifiedresources.MethodUI,
Timestamp: at,
Actor: "operator",
}}
}
// Approved, then terminally refused before dispatch (plan drift).
refused := pulseTelemetryActionRecord("refused-pre-dispatch", now.Add(-3*time.Hour), unifiedresources.ActionStateApproved, true, approvedAt(now.Add(-3*time.Hour)))
refused.Plan.ExpiresAt = now.Add(time.Hour)
if err := store.RecordActionAudit(refused); err != nil {
t.Fatalf("RecordActionAudit(refused): %v", err)
}
if _, err := actionlifecycle.RecordRefusedExecution(store, refused, "operator", now.Add(-170*time.Minute), unifiedresources.ErrActionPlanDrift); err != nil {
t.Fatalf("RecordRefusedExecution(refused): %v", err)
}
// Approved, dispatched, execution failed on the agent.
execFailed := pulseTelemetryActionRecord("exec-failed", now.Add(-2*time.Hour), unifiedresources.ActionStateFailed, true, approvedAt(now.Add(-2*time.Hour)))
execFailed.Result = &unifiedresources.ExecutionResult{Success: false, ErrorMessage: "image pull failed"}
if err := store.RecordActionAudit(execFailed); err != nil {
t.Fatalf("RecordActionAudit(execFailed): %v", err)
}
// Approved, execution succeeded, but no verification evidence confirmed it.
unverified := pulseTelemetryActionRecord("completed-unverified", now.Add(-100*time.Minute), unifiedresources.ActionStateCompleted, true, approvedAt(now.Add(-100*time.Minute)))
unverified.Result = &unifiedresources.ExecutionResult{Success: true}
if err := store.RecordActionAudit(unverified); err != nil {
t.Fatalf("RecordActionAudit(unverified): %v", err)
}
// Approved and abandoned in executing state well past the dispatch window.
stuck := pulseTelemetryActionRecord("stuck-executing", now.Add(-5*time.Hour), unifiedresources.ActionStateExecuting, true, approvedAt(now.Add(-5*time.Hour)))
if err := store.RecordActionAudit(stuck); err != nil {
t.Fatalf("RecordActionAudit(stuck): %v", err)
}
// Approved and still legitimately in flight: no failure bucket.
inFlight := pulseTelemetryActionRecord("in-flight", now.Add(-5*time.Minute), unifiedresources.ActionStateExecuting, true, approvedAt(now.Add(-5*time.Minute)))
if err := store.RecordActionAudit(inFlight); err != nil {
t.Fatalf("RecordActionAudit(inFlight): %v", err)
}
// Approved and verified success: not a failure.
verified := pulseTelemetryActionRecord("verified-success", now.Add(-90*time.Minute), unifiedresources.ActionStateCompleted, true, approvedAt(now.Add(-90*time.Minute)))
verified.Result = &unifiedresources.ExecutionResult{Success: true}
verified.VerificationOutcome = unifiedresources.VerificationOutcome{Status: unifiedresources.VerificationVerified}
if err := store.RecordActionAudit(verified); err != nil {
t.Fatalf("RecordActionAudit(verified): %v", err)
}
got := router.GetPulseIntelligenceActionTelemetry(since)
if got.ApprovedActionAttempts30d != 6 {
t.Fatalf("ApprovedActionAttempts30d = %d, want 6", got.ApprovedActionAttempts30d)
}
if got.ApprovedActionSuccesses30d != 1 {
t.Fatalf("ApprovedActionSuccesses30d = %d, want 1", got.ApprovedActionSuccesses30d)
}
if got.ApprovedActionFailuresPreDispatch30d != 1 {
t.Fatalf("ApprovedActionFailuresPreDispatch30d = %d, want 1", got.ApprovedActionFailuresPreDispatch30d)
}
if got.ApprovedActionFailuresExecution30d != 1 {
t.Fatalf("ApprovedActionFailuresExecution30d = %d, want 1", got.ApprovedActionFailuresExecution30d)
}
if got.ApprovedActionFailuresUnverified30d != 1 {
t.Fatalf("ApprovedActionFailuresUnverified30d = %d, want 1", got.ApprovedActionFailuresUnverified30d)
}
if got.ApprovedActionStuckExecuting30d != 1 {
t.Fatalf("ApprovedActionStuckExecuting30d = %d, want 1", got.ApprovedActionStuckExecuting30d)
}
if got.ApprovedActionInFlight30d != 1 {
t.Fatalf("ApprovedActionInFlight30d = %d, want 1", got.ApprovedActionInFlight30d)
}
if got.ApprovedActionUnclassified30d != 0 {
t.Fatalf("ApprovedActionUnclassified30d = %d, want 0", got.ApprovedActionUnclassified30d)
}
if got.ApprovedActionRefusalsPlanStale30d != 1 ||
got.ApprovedActionRefusalsPolicy30d != 0 ||
got.ApprovedActionRefusalsCapability30d != 0 ||
got.ApprovedActionRefusalsTargetChanged30d != 0 ||
got.ApprovedActionRefusalsPrerequisite30d != 0 ||
got.ApprovedActionRefusalsContract30d != 0 ||
got.ApprovedActionRefusalsUncoded30d != 0 ||
got.ApprovedActionRefusalsOther30d != 0 {
t.Fatalf(
"refusal categories = %d/%d/%d/%d/%d/%d/%d/%d, want 1/0/0/0/0/0/0/0",
got.ApprovedActionRefusalsPlanStale30d,
got.ApprovedActionRefusalsPolicy30d,
got.ApprovedActionRefusalsCapability30d,
got.ApprovedActionRefusalsTargetChanged30d,
got.ApprovedActionRefusalsPrerequisite30d,
got.ApprovedActionRefusalsContract30d,
got.ApprovedActionRefusalsUncoded30d,
got.ApprovedActionRefusalsOther30d,
)
}
// The completed-unverified record is the most recent failure; the legacy
// row carries no canonical reason code, so the sanitized fallback applies.
if got.ApprovedActionLastFailureReason30d != "verification_unconfirmed" {
t.Fatalf("ApprovedActionLastFailureReason30d = %q, want %q", got.ApprovedActionLastFailureReason30d, "verification_unconfirmed")
}
}
func TestGetPulseIntelligenceActionTelemetry_CountsOnlyIndependentPatrolFindingResolutions(t *testing.T) {
now := time.Now().UTC()
since := now.Add(-telemetry.PulseIntelligenceTelemetryWindow)
router := &Router{
resourceHandlers: NewResourceHandlers(&config.Config{DataPath: t.TempDir()}),
}
store, err := router.resourceHandlers.getStore("default")
if err != nil {
t.Fatalf("getStore: %v", err)
}
approved := []unifiedresources.ActionApprovalRecord{{
Outcome: unifiedresources.OutcomeApproved, Method: unifiedresources.MethodUI,
Timestamp: now.Add(-time.Hour), Actor: "operator",
}}
makeCompleted := func(id string, evidenceClass unifiedresources.ActionEvidenceClass, origin *unifiedresources.ActionOrigin) unifiedresources.ActionAuditRecord {
record := pulseTelemetryActionRecord(id, now.Add(-time.Hour), unifiedresources.ActionStateCompleted, true, approved)
truth := apiActionResultTruth(t, unifiedresources.ActionExecutionSucceeded, unifiedresources.ActionVerificationConfirmed, evidenceClass)
record.Result = &unifiedresources.ExecutionResult{ActionResultV2: &truth}
record.Origin = origin
return record
}
for _, record := range []unifiedresources.ActionAuditRecord{
makeCompleted("linked-independent", unifiedresources.ActionEvidenceIndependent, &unifiedresources.ActionOrigin{
Surface: "patrol", FindingID: "finding-1", InvestigationID: "investigation-1",
}),
makeCompleted("linked-agent-attested", unifiedresources.ActionEvidenceAgentAttested, &unifiedresources.ActionOrigin{
Surface: "patrol", FindingID: "finding-2", InvestigationID: "investigation-2",
}),
makeCompleted("independent-without-finding", unifiedresources.ActionEvidenceIndependent, nil),
} {
if err := store.RecordActionAudit(record); err != nil {
t.Fatalf("RecordActionAudit(%s): %v", record.ID, err)
}
}
got := router.GetPulseIntelligenceActionTelemetry(since)
if got.ApprovedActionAttempts30d != 3 || got.ApprovedActionSuccesses30d != 3 {
t.Fatalf("attempts/successes = %d/%d, want 3/3", got.ApprovedActionAttempts30d, got.ApprovedActionSuccesses30d)
}
if got.VerifiedFindingResolutions30d != 1 {
t.Fatalf("VerifiedFindingResolutions30d = %d, want 1", got.VerifiedFindingResolutions30d)
}
}
func TestGetPulseIntelligenceActionTelemetry_LastFailureReasonUsesCanonicalReasonCode(t *testing.T) {
now := time.Now().UTC()
since := now.Add(-telemetry.PulseIntelligenceTelemetryWindow)
dataDir := t.TempDir()
router := &Router{
resourceHandlers: NewResourceHandlers(&config.Config{DataPath: dataDir}),
}
store, err := router.resourceHandlers.getStore("default")
if err != nil {
t.Fatalf("getStore: %v", err)
}
refused := pulseTelemetryActionRecord("refused-plan-drift", now.Add(-time.Hour), unifiedresources.ActionStateApproved, true, []unifiedresources.ActionApprovalRecord{{
Outcome: unifiedresources.OutcomeApproved,
Method: unifiedresources.MethodUI,
Timestamp: now.Add(-time.Hour),
Actor: "operator",
}})
refused.Plan.ExpiresAt = now.Add(time.Hour)
if err := store.RecordActionAudit(refused); err != nil {
t.Fatalf("RecordActionAudit(refused): %v", err)
}
if _, err := actionlifecycle.RecordRefusedExecution(store, refused, "operator", now.Add(-30*time.Minute), unifiedresources.ErrActionPlanDrift); err != nil {
t.Fatalf("RecordRefusedExecution(refused): %v", err)
}
got := router.GetPulseIntelligenceActionTelemetry(since)
if got.ApprovedActionFailuresPreDispatch30d != 1 {
t.Fatalf("ApprovedActionFailuresPreDispatch30d = %d, want 1", got.ApprovedActionFailuresPreDispatch30d)
}
if got.ApprovedActionLastFailureReason30d != "plan_drift" {
t.Fatalf("ApprovedActionLastFailureReason30d = %q, want %q", got.ApprovedActionLastFailureReason30d, "plan_drift")
}
}
func TestPulseIntelligenceApprovedActionRefusalCategory(t *testing.T) {
tests := map[string]string{
"plan_drift": "plan_stale",
"action_plan_expired": "plan_stale",
"action_replan_required": "plan_stale",
"resource_remediation_locked": "policy",
"policy_authorization_expired": "policy",
"action_emergency_stop": "policy",
"action_dry_run_only": "capability",
"action_execution_unavailable": "capability",
"agent_capability_unavailable": "capability",
"target_state_changed": "target_changed",
"target_precondition_failed": "target_changed",
"package_inventory_changed": "target_changed",
"cleanup_inventory_changed": "target_changed",
"target_inspection_unavailable": "prerequisite",
"package_manager_busy": "prerequisite",
"package_index_refresh_failed": "prerequisite",
"package_preflight_failed": "prerequisite",
"package_manager_unhealthy": "prerequisite",
"cleanup_preflight_failed": "prerequisite",
"action_contract_invalid": "contract",
"preflight_refused": "uncoded",
"pre_dispatch_refused": "uncoded",
"future_reason": "other",
}
for reason, want := range tests {
if got := pulseIntelligenceApprovedActionRefusalCategory(reason); got != want {
t.Errorf("reason %q category = %q, want %q", reason, got, want)
}
}
}
func pulseTelemetryActionRecord(
id string,
createdAt time.Time,
state unifiedresources.ActionState,
requiresApproval bool,
approvals []unifiedresources.ActionApprovalRecord,
) unifiedresources.ActionAuditRecord {
approvalPolicy := unifiedresources.ApprovalNone
if requiresApproval {
approvalPolicy = unifiedresources.ApprovalAdmin
}
return unifiedresources.ActionAuditRecord{
ID: id,
CreatedAt: createdAt,
UpdatedAt: createdAt,
State: state,
Request: unifiedresources.ActionRequest{
RequestID: "req-" + id,
ResourceID: "vm:101",
CapabilityName: "pulse_exec",
Reason: "test",
RequestedBy: "pulse_assistant",
Actor: unifiedresources.ActionActor{SubjectID: "pulse_assistant", Kind: unifiedresources.ActionActorService, CredentialID: "service:test", OrgID: "default"},
},
Plan: unifiedresources.ActionPlan{
ActionID: id,
RequestID: "req-" + id,
Allowed: true,
RequiresApproval: requiresApproval,
ApprovalPolicy: approvalPolicy,
ApprovalRequirement: unifiedresources.ApprovalRequirementForFloor(approvalPolicy),
PlannedAt: createdAt,
ExpiresAt: createdAt.Add(time.Hour),
PlanHash: "sha256:" + id,
},
Approvals: approvals,
}
}