diff --git a/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs b/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs
new file mode 100644
index 0000000..a95bc15
--- /dev/null
+++ b/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs
@@ -0,0 +1,17 @@
+using ScadaLink.Commons.Entities.Audit;
+
+namespace ScadaLink.Commons.Interfaces.Services;
+
+///
+/// Boundary-side abstraction for emitting Audit Log (#23) events.
+/// Implementations on the site write to local SQLite hot-path; on central they write to MS SQL directly.
+/// Failures must NEVER abort the user-facing action.
+///
+public interface IAuditWriter
+{
+ ///
+ /// Persist an audit event. Best-effort: implementations must swallow/log internal failures
+ /// rather than propagating them to the calling boundary code.
+ ///
+ Task WriteAsync(AuditEvent evt, CancellationToken ct = default);
+}
diff --git a/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs b/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs
new file mode 100644
index 0000000..d91f534
--- /dev/null
+++ b/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs
@@ -0,0 +1,16 @@
+using ScadaLink.Commons.Entities.Audit;
+
+namespace ScadaLink.Commons.Interfaces.Services;
+
+///
+/// Central-only audit writer for the direct-write path (Notification Outbox dispatch, Inbound API).
+/// Distinct from so DI binding can differ between site and central hosts.
+///
+public interface ICentralAuditWriter
+{
+ ///
+ /// Persist an audit event into the central AuditLog table directly (bypassing site telemetry).
+ /// Best-effort: implementations must swallow/log internal failures rather than propagating them.
+ ///
+ Task WriteAsync(AuditEvent evt, CancellationToken ct = default);
+}
diff --git a/tests/ScadaLink.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs b/tests/ScadaLink.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs
new file mode 100644
index 0000000..c40a5d2
--- /dev/null
+++ b/tests/ScadaLink.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using ScadaLink.Commons.Entities.Audit;
+using ScadaLink.Commons.Interfaces.Services;
+
+namespace ScadaLink.Commons.Tests.Interfaces.Services;
+
+///
+/// Reflection-level contract guards for the Audit Log (#23) writer interfaces.
+/// Locks in method signature so DI bindings + adapter implementations stay aligned.
+///
+public class AuditWriterContractTests
+{
+ [Theory]
+ [InlineData(typeof(IAuditWriter))]
+ [InlineData(typeof(ICentralAuditWriter))]
+ public void WriteAsync_HasExpectedSignature(Type writerType)
+ {
+ var method = writerType.GetMethod("WriteAsync", BindingFlags.Instance | BindingFlags.Public);
+ Assert.NotNull(method);
+ Assert.Equal(typeof(Task), method!.ReturnType);
+
+ var parameters = method.GetParameters();
+ Assert.Equal(2, parameters.Length);
+ Assert.Equal(typeof(AuditEvent), parameters[0].ParameterType);
+ Assert.Equal(typeof(CancellationToken), parameters[1].ParameterType);
+ Assert.True(parameters[1].HasDefaultValue);
+ }
+
+ [Fact]
+ public void IAuditWriter_AndICentralAuditWriter_AreDistinctTypes()
+ {
+ Assert.NotEqual(typeof(IAuditWriter), typeof(ICentralAuditWriter));
+ Assert.False(typeof(IAuditWriter).IsAssignableFrom(typeof(ICentralAuditWriter)));
+ Assert.False(typeof(ICentralAuditWriter).IsAssignableFrom(typeof(IAuditWriter)));
+ }
+}