feat(auditlog): inbound audit rows carry ExecutionId

This commit is contained in:
Joseph Doherty
2026-05-21 15:44:17 -04:00
parent 6aac4c8ed7
commit cfd8f1ecf4
2 changed files with 19 additions and 10 deletions

View File

@@ -145,17 +145,21 @@ public sealed class AuditWriteMiddleware
OccurredAtUtc = DateTime.UtcNow,
Channel = AuditChannel.ApiInbound,
Kind = kind,
// Audit Log #23: a fresh per-request correlation id so the
// Audit Log #23: a fresh per-request execution id so the
// inbound row carries a request identifier (closes the design
// gap that inbound rows should be correlatable).
//
// This id is intentionally request-local: it is NOT bridged to
// RouteHelper's routed-call correlation id or to
// HttpContext.TraceIdentifier. Threading an inbound request's
// correlation id through to the routed script execution (so an
// execution id through to the routed script execution (so an
// inbound call and the outbound API/DB rows it triggers share
// one id) is a deliberate future follow-up, out of scope here.
CorrelationId = Guid.NewGuid(),
ExecutionId = Guid.NewGuid(),
// CorrelationId is purely the per-operation-lifecycle id; an
// inbound request is a one-shot from the audit row's
// perspective with no multi-row operation to correlate.
CorrelationId = null,
Actor = actor,
Target = methodName,
Status = status,

View File

@@ -351,12 +351,14 @@ public class AuditWriteMiddlewareTests
}
// ---------------------------------------------------------------------
// Correlation id — Audit Log #23: each inbound row carries a fresh
// per-request correlation id so inbound rows are correlatable.
// Execution id — Audit Log #23: each inbound row carries a fresh
// per-request execution id so inbound rows are correlatable. The inbound
// row's CorrelationId stays null — CorrelationId is purely the
// per-operation-lifecycle id and an inbound request is a one-shot.
// ---------------------------------------------------------------------
[Fact]
public async Task InboundRow_CarriesNonNull_CorrelationId()
public async Task InboundRow_CarriesNonNull_ExecutionId_And_NullCorrelationId()
{
var writer = new RecordingAuditWriter();
var ctx = BuildContext();
@@ -369,12 +371,15 @@ public class AuditWriteMiddlewareTests
await mw.InvokeAsync(ctx);
var evt = Assert.Single(writer.Events);
Assert.NotNull(evt.CorrelationId);
Assert.NotEqual(Guid.Empty, evt.CorrelationId!.Value);
Assert.NotNull(evt.ExecutionId);
Assert.NotEqual(Guid.Empty, evt.ExecutionId!.Value);
// CorrelationId is the per-operation-lifecycle id; an inbound request
// is a one-shot with no multi-row operation to correlate.
Assert.Null(evt.CorrelationId);
}
[Fact]
public async Task SeparateRequests_GetDistinct_CorrelationIds()
public async Task SeparateRequests_GetDistinct_ExecutionIds()
{
var writer = new RecordingAuditWriter();
var mw = CreateMiddleware(hc =>
@@ -387,7 +392,7 @@ public class AuditWriteMiddlewareTests
await mw.InvokeAsync(BuildContext());
Assert.Equal(2, writer.Events.Count);
Assert.NotEqual(writer.Events[0].CorrelationId, writer.Events[1].CorrelationId);
Assert.NotEqual(writer.Events[0].ExecutionId, writer.Events[1].ExecutionId);
}
[Fact]