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()
{