feat(audit): replay/pull include outbound API, DB and notification rows

GetTraceEventsAsync widens its channel filter to ScriptTrace ∪ ApiOutbound ∪
DbOutbound ∪ Notification ∪ (ApiInbound & InboundRequest) (plan D6); SecuredWrite,
Cluster and InboundAuthFailure rows in the same tree stay excluded. The site
SqliteAuditWriter.ReadByExecutionIdsAsync already had no channel predicate — pinned
by a test. Interface doc updated to match.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-06 01:47:05 -04:00
parent 29129ef17a
commit 30faee0d0e
5 changed files with 115 additions and 11 deletions
@@ -72,6 +72,37 @@ public class SqliteAuditWriterReadByExecutionTests : IDisposable
}
}
/// <summary>
/// Outbound-calls plan (2026-09-06, D6): the site pull filters by execution id ONLY — an
/// ApiOutbound row (or any other channel) stored under the execution id is served alongside
/// the ScriptTrace rows, in time order. Pins that no channel predicate creeps in.
/// </summary>
[Fact]
public async Task ServesOutboundRows_NoChannelPredicate()
{
var root = Guid.NewGuid();
var siteRun = Guid.NewGuid();
var run = Row(T0.AddSeconds(1), siteRun, root);
var apiCall = ScadaBridgeAuditEventFactory.Create(AuditChannel.ApiOutbound, AuditKind.CachedSubmit, AuditStatus.Submitted,
occurredAtUtc: T0.AddSeconds(2), target: "Delmia.AckRecipe", executionId: siteRun, parentExecutionId: root,
sourceSiteId: "site-a", correlationId: Guid.NewGuid());
var dbWrite = ScadaBridgeAuditEventFactory.Create(AuditChannel.DbOutbound, AuditKind.DbWrite, AuditStatus.Delivered,
occurredAtUtc: T0.AddSeconds(3), target: "MesDb", executionId: siteRun, parentExecutionId: root, sourceSiteId: "site-a");
var notify = ScadaBridgeAuditEventFactory.Create(AuditChannel.Notification, AuditKind.NotifySend, AuditStatus.Submitted,
occurredAtUtc: T0.AddSeconds(4), target: "Planners", executionId: siteRun, parentExecutionId: root,
sourceSiteId: "site-a", correlationId: Guid.NewGuid());
await using var writer = CreateWriter();
foreach (var r in new[] { notify, apiCall, run, dbWrite }) await writer.WriteAsync(r);
var hits = await writer.ReadByExecutionIdsAsync([siteRun], T0, limit: 100);
Assert.Equal(
new[] { run.EventId, apiCall.EventId, dbWrite.EventId, notify.EventId },
hits.Select(h => h.EventId).ToArray());
Assert.Equal(apiCall, hits[1]);
Assert.Equal(AuditChannel.ApiOutbound, AuditRowProjection.Decompose(hits[1]).Channel);
}
[Fact]
public async Task Guards_LimitAndIdCap()
{