From cfd8f1ecf4701f2016bb4887d0bda03a2104d9d8 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 21 May 2026 15:44:17 -0400 Subject: [PATCH] feat(auditlog): inbound audit rows carry ExecutionId --- .../Middleware/AuditWriteMiddleware.cs | 10 +++++++--- .../Middleware/AuditWriteMiddlewareTests.cs | 19 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/ScadaLink.InboundAPI/Middleware/AuditWriteMiddleware.cs b/src/ScadaLink.InboundAPI/Middleware/AuditWriteMiddleware.cs index c271d03..b4b8410 100644 --- a/src/ScadaLink.InboundAPI/Middleware/AuditWriteMiddleware.cs +++ b/src/ScadaLink.InboundAPI/Middleware/AuditWriteMiddleware.cs @@ -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, diff --git a/tests/ScadaLink.InboundAPI.Tests/Middleware/AuditWriteMiddlewareTests.cs b/tests/ScadaLink.InboundAPI.Tests/Middleware/AuditWriteMiddlewareTests.cs index 0bf8f0b..436507f 100644 --- a/tests/ScadaLink.InboundAPI.Tests/Middleware/AuditWriteMiddlewareTests.cs +++ b/tests/ScadaLink.InboundAPI.Tests/Middleware/AuditWriteMiddlewareTests.cs @@ -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]