fix(site-runtime): trace sink is guarded — a throwing observer can neither fail a call nor skip the audit write

Review of Task 3: the sink invoke sat before WriteAsync/ForwardAsync inside the
best-effort try, so a throwing sink would have silently dropped the durable
row. GuardTraceSink wraps the delegate in every helper ctor (and Collect
guards EmitTrace); pinned by a throwing-sink test. ScriptRunLauncher no longer
allocates a collector for a traced run with no reply target (Nobody) — the
list would never be read. FlowTraceCollector.Complete pinned at unit level;
Snapshot documented as a non-closing peek.

Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
Joseph Doherty
2026-09-06 01:57:09 -04:00
parent 69feb40e65
commit ec9011429c
5 changed files with 92 additions and 8 deletions
@@ -72,7 +72,11 @@ public sealed class FlowTraceCollector
/// <summary>Current event count.</summary>
public int Count { get { lock (_gate) return _events.Count; } }
/// <summary>Copies the collected events in emission order.</summary>
/// <summary>
/// Copies the collected events in emission order WITHOUT closing the collector — a diagnostic /
/// test-only peek. A reply path must use <see cref="Complete"/>, otherwise an un-awaited call
/// finishing after the reply could mutate the list the reply was built from.
/// </summary>
public IReadOnlyList<AuditEvent> Snapshot()
{
lock (_gate) return _events.ToArray();