feat(auditlog): NotifyDeliver rows carry the originating ParentExecutionId

This commit is contained in:
Joseph Doherty
2026-05-21 18:11:04 -04:00
parent c00603e2a4
commit d35551efc2
16 changed files with 2056 additions and 9 deletions

View File

@@ -88,7 +88,8 @@ public class NotificationOutboxActorTerminalEmissionTests : TestKit
NotificationStatus status = NotificationStatus.Pending,
int retryCount = 0,
Guid? notificationId = null,
Guid? originExecutionId = null)
Guid? originExecutionId = null,
Guid? originParentExecutionId = null)
{
return new Notification(
(notificationId ?? Guid.NewGuid()).ToString("D"),
@@ -102,6 +103,7 @@ public class NotificationOutboxActorTerminalEmissionTests : TestKit
RetryCount = retryCount,
CreatedAt = DateTimeOffset.UtcNow,
OriginExecutionId = originExecutionId,
OriginParentExecutionId = originParentExecutionId,
};
}
@@ -191,6 +193,50 @@ public class NotificationOutboxActorTerminalEmissionTests : TestKit
});
}
[Fact]
public void Terminal_Delivered_CarriesOriginParentExecutionId_AsParentExecutionId()
{
// Audit Log ParentExecutionId: the terminal NotifyDeliver row must echo
// the notification's OriginParentExecutionId so the central dispatcher's
// rows carry the routed run's parent id.
SetupSmtpRetryPolicy(maxRetries: 5, retryDelay: TimeSpan.FromMinutes(1));
var parentExecutionId = Guid.NewGuid();
var notification = MakeNotification(originParentExecutionId: parentExecutionId);
_outboxRepository.GetDueAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.Returns(new[] { notification });
var adapter = new StubAdapter(() => DeliveryOutcome.Success("ops@example.com"));
var actor = CreateActor([adapter]);
actor.Tell(InternalMessages.DispatchTick.Instance);
AwaitAssert(() =>
{
var delivered = EventsByStatus(AuditStatus.Delivered);
Assert.Single(delivered);
Assert.Equal(parentExecutionId, delivered[0].ParentExecutionId);
});
}
[Fact]
public void Terminal_Delivered_NullOriginParentExecutionId_HasNullParentExecutionId()
{
SetupSmtpRetryPolicy(maxRetries: 5, retryDelay: TimeSpan.FromMinutes(1));
var notification = MakeNotification(originParentExecutionId: null);
_outboxRepository.GetDueAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
.Returns(new[] { notification });
var adapter = new StubAdapter(() => DeliveryOutcome.Success("ops@example.com"));
var actor = CreateActor([adapter]);
actor.Tell(InternalMessages.DispatchTick.Instance);
AwaitAssert(() =>
{
var delivered = EventsByStatus(AuditStatus.Delivered);
Assert.Single(delivered);
Assert.Null(delivered[0].ParentExecutionId);
});
}
[Fact]
public void Terminal_Parked_OnPermanentFailure_EmitsEvent_StatusParked()
{