diff --git a/src/Backend/AutopilotMonitor.Functions/Services/Analyze/AzureQueueAnalyzeOnEnrollmentEndProducer.cs b/src/Backend/AutopilotMonitor.Functions/Services/Analyze/AzureQueueAnalyzeOnEnrollmentEndProducer.cs
index 460c8233..d992ab1f 100644
--- a/src/Backend/AutopilotMonitor.Functions/Services/Analyze/AzureQueueAnalyzeOnEnrollmentEndProducer.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Services/Analyze/AzureQueueAnalyzeOnEnrollmentEndProducer.cs
@@ -28,6 +28,19 @@ namespace AutopilotMonitor.Functions.Services.Analyze
private int _queueEnsured; // 0 = not yet ensured, 1 = CreateIfNotExistsAsync has run
+ ///
+ /// Delay before the enqueued message becomes visible to the worker. The trigger event
+ /// (enrollment_complete / enrollment_failed) is by design the FIRST event of the
+ /// agent's terminal sequence — the terminal flush (stuck-app promotion's synthetic
+ /// app_install_failed, app_tracking_summary, esp_apps_failure_correlation,
+ /// agent_shutting_down) lands in separate uploads seconds later. An immediately
+ /// visible message let the worker evaluate rules against a stream missing those rows
+ /// (session 53d1e9f6: ANALYZE-ESP-004's optional app_install_failed condition never
+ /// matched, {{appName}} stayed a literal placeholder — and the RuleResult dedup makes
+ /// that permanent). 30 s covers the flush including the agent's late-event grace.
+ ///
+ private static readonly TimeSpan s_visibilityDelay = TimeSpan.FromSeconds(30);
+
public AzureQueueAnalyzeOnEnrollmentEndProducer(
QueueClientFactory queueFactory,
ILogger logger)
@@ -53,7 +66,10 @@ namespace AutopilotMonitor.Functions.Services.Analyze
try
{
var body = JsonConvert.SerializeObject(envelope);
- await _queueClient.SendMessageAsync(body, cancellationToken).ConfigureAwait(false);
+ await _queueClient.SendMessageAsync(
+ body,
+ visibilityTimeout: s_visibilityDelay,
+ cancellationToken: cancellationToken).ConfigureAwait(false);
_logger.LogInformation(
"Analyze enqueued (tenant={Tenant} session={Session} reason={Reason})",
envelope.TenantId, envelope.SessionId, envelope.Reason);