feat(notify): dual-write audit events to Timeline unified sink
Sprint SPRINT_20260408_005 DEPRECATE-001 (Notify, third service). Same pattern as Authority + Policy dual-write: NotifyAuditRepository now fans out to Timeline via the optional IAuditEventEmitter. Fire-and-forget; local write stays authoritative. Remaining DEPRECATE-001 services: Scheduler (ISchedulerAuditService), JobEngine/ReleaseOrchestrator (PostgresAuditRepository.AppendAsync), Attestor (audit log inserts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Audit.Emission;
|
||||
using StellaOps.Notify.Persistence.EfCore.Context;
|
||||
using StellaOps.Notify.Persistence.Postgres.Models;
|
||||
|
||||
@@ -10,11 +11,13 @@ public sealed class NotifyAuditRepository : INotifyAuditRepository
|
||||
private const int CommandTimeoutSeconds = 30;
|
||||
private readonly NotifyDataSource _dataSource;
|
||||
private readonly ILogger<NotifyAuditRepository> _logger;
|
||||
private readonly IAuditEventEmitter? _timelineEmitter;
|
||||
|
||||
public NotifyAuditRepository(NotifyDataSource dataSource, ILogger<NotifyAuditRepository> logger)
|
||||
public NotifyAuditRepository(NotifyDataSource dataSource, ILogger<NotifyAuditRepository> logger, IAuditEventEmitter? timelineEmitter = null)
|
||||
{
|
||||
_dataSource = dataSource;
|
||||
_logger = logger;
|
||||
_timelineEmitter = timelineEmitter;
|
||||
}
|
||||
|
||||
public async Task<long> CreateAsync(NotifyAuditEntity audit, CancellationToken cancellationToken = default)
|
||||
@@ -23,9 +26,60 @@ public sealed class NotifyAuditRepository : INotifyAuditRepository
|
||||
await using var dbContext = NotifyDbContextFactory.Create(connection, CommandTimeoutSeconds, GetSchemaName());
|
||||
dbContext.Audit.Add(audit);
|
||||
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// DEPRECATE-001: dual-write to Timeline. Fire-and-forget; local write remains authoritative.
|
||||
if (_timelineEmitter is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _timelineEmitter.EmitAsync(MapToTimelinePayload(audit), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to emit notify audit event to Timeline (local write succeeded, id={AuditId})", audit.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return audit.Id;
|
||||
}
|
||||
|
||||
private static AuditEventPayload MapToTimelinePayload(NotifyAuditEntity audit)
|
||||
{
|
||||
var details = new Dictionary<string, object?>(StringComparer.Ordinal)
|
||||
{
|
||||
["localAuditId"] = audit.Id
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(audit.Details))
|
||||
{
|
||||
details["details"] = audit.Details;
|
||||
}
|
||||
|
||||
return new AuditEventPayload
|
||||
{
|
||||
Id = $"notify-{audit.Id}",
|
||||
Timestamp = audit.CreatedAt,
|
||||
Module = "notify",
|
||||
Action = audit.Action ?? "unknown",
|
||||
Severity = "info",
|
||||
Actor = new AuditActorPayload
|
||||
{
|
||||
Id = audit.UserId?.ToString() ?? "notify-system",
|
||||
Name = audit.UserId?.ToString() ?? "notify-system",
|
||||
Type = audit.UserId.HasValue ? "user" : "system"
|
||||
},
|
||||
Resource = new AuditResourcePayload
|
||||
{
|
||||
Type = audit.ResourceType ?? "notify_resource",
|
||||
Id = audit.ResourceId ?? audit.Id.ToString(System.Globalization.CultureInfo.InvariantCulture)
|
||||
},
|
||||
Description = audit.Action ?? "notify audit event",
|
||||
Details = details,
|
||||
CorrelationId = audit.CorrelationId,
|
||||
TenantId = audit.TenantId,
|
||||
Tags = new[] { "notify", audit.Action ?? "unknown" }
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<NotifyAuditEntity>> ListAsync(string tenantId, int limit = 100, int offset = 0, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(tenantId, "reader", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Audit.Emission\StellaOps.Audit.Emission.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Notify.Models\StellaOps.Notify.Models.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.EfCore\StellaOps.Infrastructure.EfCore.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user