Files
pulse/internal/api/telemetry_pulse_intelligence_test.go
T

524 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.PatrolActionPlans30d != 2 || got.PatrolApprovalRequests30d != 2 {
t.Fatalf("Patrol-origin plans/approval requests = %d/%d, want 2/2", got.PatrolActionPlans30d, got.PatrolApprovalRequests30d)
}
if got.PatrolRejectedActionDecisions30d != 0 || got.PatrolApprovedActionDecisions30d != 2 {
t.Fatalf("Patrol-origin rejected/approved decisions = %d/%d, want 0/2", got.PatrolRejectedActionDecisions30d, got.PatrolApprovedActionDecisions30d)
}
if got.PatrolApprovedActionAttempts30d != 2 || got.PatrolApprovedActionSuccesses30d != 2 {
t.Fatalf("Patrol-origin attempts/successes = %d/%d, want 2/2", got.PatrolApprovedActionAttempts30d, got.PatrolApprovedActionSuccesses30d)
}
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,
}
}