feat(audit)!: ScadaBridge C3 — swap to canonical ZB.MOM.WW.Audit.AuditEvent across seams/emitters/DTO/redactor wiring; transitional 24-col storage shim (Task 2.5)

This commit is contained in:
Joseph Doherty
2026-06-02 12:37:50 -04:00
parent 5aaf9e2923
commit db707bb0de
127 changed files with 2240 additions and 3886 deletions
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
@@ -127,22 +127,26 @@ public static class AuditEndpoints
var paging = ParsePaging(context.Request.Query);
var repo = context.RequestServices.GetRequiredService<IAuditLogRepository>();
var events = await repo.QueryAsync(filter, paging, context.RequestAborted);
var canonical = await repo.QueryAsync(filter, paging, context.RequestAborted);
// The cursor for the next page is the last row of this page — but only
// when the page came back FULL. A short page means there is no next
// page, so nextCursor is null and the CLI stops paging.
object? nextCursor = null;
if (events.Count == paging.PageSize && events.Count > 0)
if (canonical.Count == paging.PageSize && canonical.Count > 0)
{
var last = events[^1];
var last = canonical[^1];
nextCursor = new
{
afterOccurredAtUtc = last.OccurredAtUtc,
// C3: canonical OccurredAtUtc is a DateTimeOffset; the cursor key is UTC.
afterOccurredAtUtc = last.OccurredAtUtc.UtcDateTime,
afterEventId = last.EventId,
};
}
// C3 (Task 2.5): decompose canonical rows into the flat AuditExportRow so the
// CLI's JSON shape (24-field) is unchanged.
var events = canonical.Select(AuditExportRow.From).ToList();
var payload = new { events, nextCursor };
// EnvelopeJsonOptions keeps an explicit null nextCursor on the wire so
// the CLI can always read the key. AuditEvent rows render with their
@@ -248,7 +252,7 @@ public static class AuditEndpoints
{
foreach (var evt in page)
{
await writer.WriteLineAsync(FormatCsvRow(evt));
await writer.WriteLineAsync(FormatCsvRow(AuditExportRow.From(evt)));
}
await writer.FlushAsync(ct);
await output.FlushAsync(ct);
@@ -275,7 +279,7 @@ public static class AuditEndpoints
{
foreach (var evt in page)
{
await writer.WriteLineAsync(JsonSerializer.Serialize(evt, JsonOptions));
await writer.WriteLineAsync(JsonSerializer.Serialize(AuditExportRow.From(evt), JsonOptions));
}
await writer.FlushAsync(ct);
await output.FlushAsync(ct);
@@ -309,7 +313,8 @@ public static class AuditEndpoints
}
var last = page[^1];
cursor = new AuditLogPaging(ExportPageSize, last.OccurredAtUtc, last.EventId);
// C3: canonical OccurredAtUtc is a DateTimeOffset; the keyset cursor column is UTC.
cursor = new AuditLogPaging(ExportPageSize, last.OccurredAtUtc.UtcDateTime, last.EventId);
}
}
@@ -571,7 +576,7 @@ public static class AuditEndpoints
/// Formats a single <see cref="AuditEvent"/> as an RFC 4180 CSV row matching <see cref="CsvHeader"/>.
/// </summary>
/// <param name="evt">The audit event to format.</param>
public static string FormatCsvRow(AuditEvent evt)
public static string FormatCsvRow(AuditExportRow evt)
{
var sb = new StringBuilder(256);
AppendField(sb, evt.EventId.ToString(), first: true);