diff --git a/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/Postgres/PostgresAuditRepository.cs b/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/Postgres/PostgresAuditRepository.cs index 1afcfc55d..3caaaa19b 100644 --- a/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/Postgres/PostgresAuditRepository.cs +++ b/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/Postgres/PostgresAuditRepository.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using Npgsql; +using StellaOps.Audit.Emission; using StellaOps.ReleaseOrchestrator.Persistence.Domain; using StellaOps.ReleaseOrchestrator.Persistence.Hashing; using StellaOps.ReleaseOrchestrator.Persistence.Repositories; @@ -98,17 +99,20 @@ public sealed class PostgresAuditRepository : IAuditRepository private readonly CanonicalJsonHasher _hasher; private readonly ILogger _logger; private readonly TimeProvider _timeProvider; + private readonly IAuditEventEmitter? _timelineEmitter; public PostgresAuditRepository( ReleaseOrchestratorDataSource dataSource, CanonicalJsonHasher hasher, ILogger logger, - TimeProvider? timeProvider = null) + TimeProvider? timeProvider = null, + IAuditEventEmitter? timelineEmitter = null) { _dataSource = dataSource ?? throw new ArgumentNullException(nameof(dataSource)); _hasher = hasher ?? throw new ArgumentNullException(nameof(hasher)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _timeProvider = timeProvider ?? TimeProvider.System; + _timelineEmitter = timelineEmitter; } public async Task AppendAsync( @@ -198,6 +202,21 @@ public sealed class PostgresAuditRepository : IAuditRepository _logger.LogDebug("Audit entry {EntryId} appended for tenant {TenantId}, sequence {Sequence}", entry.EntryId, tenantId, sequenceNumber); + // DEPRECATE-001: dual-write to Timeline. Fire-and-forget; hash chain is + // service-level evidence that stays local (not emitted) — only the audit + // event summary goes to Timeline for cross-service correlation. + if (_timelineEmitter is not null) + { + try + { + await _timelineEmitter.EmitAsync(MapToTimelinePayload(entry), cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Failed to emit jobengine audit event to Timeline (local write succeeded, entryId={EntryId})", entry.EntryId); + } + } + return entry; } catch @@ -207,6 +226,53 @@ public sealed class PostgresAuditRepository : IAuditRepository } } + private static AuditEventPayload MapToTimelinePayload(AuditEntry entry) + { + var details = new Dictionary(StringComparer.Ordinal) + { + ["localEntryId"] = entry.EntryId, + ["sequenceNumber"] = entry.SequenceNumber, + ["contentHash"] = entry.ContentHash, + ["httpMethod"] = entry.HttpMethod, + ["requestPath"] = entry.RequestPath + }; + if (!string.IsNullOrWhiteSpace(entry.OldState)) + { + details["oldState"] = entry.OldState; + } + if (!string.IsNullOrWhiteSpace(entry.NewState)) + { + details["newState"] = entry.NewState; + } + + return new AuditEventPayload + { + Id = $"jobengine-{entry.EntryId}", + Timestamp = entry.OccurredAt, + Module = "jobengine", + Action = entry.EventType.ToString().ToLowerInvariant(), + Severity = "info", + Actor = new AuditActorPayload + { + Id = entry.ActorId ?? "jobengine-system", + Name = entry.ActorId ?? "jobengine-system", + Type = entry.ActorType.ToString().ToLowerInvariant(), + IpAddress = entry.ActorIp, + UserAgent = entry.UserAgent + }, + Resource = new AuditResourcePayload + { + Type = entry.ResourceType ?? "jobengine_resource", + Id = entry.ResourceId.ToString() + }, + Description = entry.Description ?? entry.EventType.ToString(), + Details = details, + CorrelationId = entry.CorrelationId, + TenantId = entry.TenantId, + Tags = new[] { "jobengine", entry.EventType.ToString().ToLowerInvariant() } + }; + } + public async Task GetByIdAsync( string tenantId, Guid entryId, diff --git a/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/StellaOps.ReleaseOrchestrator.Persistence.csproj b/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/StellaOps.ReleaseOrchestrator.Persistence.csproj index ccee21a21..02afb28db 100644 --- a/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/StellaOps.ReleaseOrchestrator.Persistence.csproj +++ b/src/ReleaseOrchestrator/__Libraries/StellaOps.ReleaseOrchestrator.Persistence/StellaOps.ReleaseOrchestrator.Persistence.csproj @@ -27,6 +27,7 @@ +