From ad625eb36d6c7b50413d94776c97fda7c09fec17 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 23 May 2026 15:45:31 -0400 Subject: [PATCH] feat(audit): add SourceNode property to AuditEvent record --- .../Entities/Audit/AuditEvent.cs | 9 ++++++++ .../Entities/Audit/AuditEventTests.cs | 23 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/ScadaLink.Commons/Entities/Audit/AuditEvent.cs b/src/ScadaLink.Commons/Entities/Audit/AuditEvent.cs index dcb656f..54399ef 100644 --- a/src/ScadaLink.Commons/Entities/Audit/AuditEvent.cs +++ b/src/ScadaLink.Commons/Entities/Audit/AuditEvent.cs @@ -43,6 +43,15 @@ public sealed record AuditEvent /// Site id where the action originated; null for central-direct events. public string? SourceSiteId { get; init; } + /// + /// The cluster node on which the event was emitted — `node-a` / `node-b` for + /// site rows (qualified by ), `central-a` / `central-b` + /// for central-originated rows. Stamped by the writing node from + /// INodeIdentityProvider; nullable so reconciled rows from a node that + /// has since been retired don't block ingest. + /// + public string? SourceNode { get; init; } + /// Instance id where the action originated, when applicable. public string? SourceInstanceId { get; init; } diff --git a/tests/ScadaLink.Commons.Tests/Entities/Audit/AuditEventTests.cs b/tests/ScadaLink.Commons.Tests/Entities/Audit/AuditEventTests.cs index 89a68fd..7327200 100644 --- a/tests/ScadaLink.Commons.Tests/Entities/Audit/AuditEventTests.cs +++ b/tests/ScadaLink.Commons.Tests/Entities/Audit/AuditEventTests.cs @@ -116,6 +116,29 @@ public class AuditEventTests Assert.Null(evt.ForwardState); } + [Fact] + public void AuditEvent_carries_SourceNode_through_init() + { + // SourceNode identifies the cluster node that emitted the event (site + // node-a/node-b or central-a/central-b). It's an additive nullable + // init-only property — defaults to null when omitted, round-trips its + // value when set, and is preserved through `with` expressions. + var evtDefault = new AuditEvent + { + EventId = Guid.NewGuid(), + OccurredAtUtc = DateTime.UtcNow, + Channel = AuditChannel.ApiOutbound, + Kind = AuditKind.ApiCall, + Status = AuditStatus.Submitted, + PayloadTruncated = false + }; + Assert.Null(evtDefault.SourceNode); + + var evtStamped = evtDefault with { SourceNode = "node-a" }; + Assert.Equal("node-a", evtStamped.SourceNode); + Assert.Null(evtDefault.SourceNode); + } + [Fact] public void With_ProducesNewInstance_WithSingleFieldChanged() {