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
@@ -299,8 +299,9 @@ public interface IAuditLogRepository
/// <summary>
/// Flow Debugger Replay (design 2026-09-05 §7.1): every row of the execution tree that
/// <paramref name="executionId"/> belongs to (walked to the root exactly as
/// <see cref="GetExecutionTreeAsync"/> does) whose channel is <c>ScriptTrace</c>, plus the
/// tree's <c>ApiInbound.InboundRequest</c> rows, ordered <c>(OccurredAtUtc, EventId)</c>.
/// <see cref="GetExecutionTreeAsync"/> does) whose channel is <c>ScriptTrace</c>,
/// <c>ApiOutbound</c>, <c>DbOutbound</c> or <c>Notification</c> (outbound-calls plan 2026-09-06, D6),
/// plus the tree's <c>ApiInbound.InboundRequest</c> rows, ordered <c>(OccurredAtUtc, EventId)</c>.
/// Empty when nothing in the store carries the id.
/// </summary>
/// <param name="executionId">Any execution id in the chain.</param>
@@ -1133,8 +1133,8 @@ VALUES
{
// Flow Debugger Replay (design 2026-09-05 §7.1): same walk as GetExecutionTreeAsync
// — up to the root, then the Edges/Chain CTE down — but projecting the chain's
// execution ids, then the rows themselves filtered to the trace channel + the
// inbound row. Two statements keep the row read on EF's canonical mapper
// execution ids, then the rows themselves filtered to the trace + outbound channels
// + the inbound row. Two statements keep the row read on EF's canonical mapper
// (RowToCanonical) instead of a second hand-mapped reader.
var conn = _context.Database.GetDbConnection();
var openedHere = false;
@@ -1241,7 +1241,15 @@ VALUES
var rows = await _context.Set<AuditLogRow>()
.AsNoTracking()
.Where(r => r.ExecutionId != null && chainIds.Contains(r.ExecutionId.Value))
// Outbound-calls plan (2026-09-06, D6): the replay set is the trace channel plus the
// three outbound channels (the script's API / DB / notification calls already sit in
// the tree under the run's execution id) plus the inbound row. SecuredWrite, Cluster
// and InboundAuthFailure rows sharing an execution id stay out — nothing the flow
// graph can draw.
.Where(r => r.Channel == AuditChannel.ScriptTrace
|| r.Channel == AuditChannel.ApiOutbound
|| r.Channel == AuditChannel.DbOutbound
|| r.Channel == AuditChannel.Notification
|| (r.Channel == AuditChannel.ApiInbound && r.Kind == AuditKind.InboundRequest))
.OrderBy(r => r.OccurredAtUtc)
.ThenBy(r => r.EventId)