feat(auditlog): thread ExecutionId through S&F for retry-loop cached rows

The store-and-forward retry loop emits the per-attempt and terminal cached
audit rows (ApiCallCached/DbWriteCached Attempted, CachedResolve) via
CachedCallLifecycleBridge from a CachedCallAttemptContext, not from the
script context. ExecutionId (and SourceScript) were not threaded through the
S&F buffer, so those rows had ExecutionId = null and SourceScript = null.

Thread both, additively, from the cached-call enqueue path:

- StoreAndForwardMessage gains ExecutionId (Guid?) / SourceScript (string?).
- StoreAndForwardStorage adds nullable execution_id / source_script columns
  via an idempotent PRAGMA-probed ALTER TABLE migration; rows persisted by
  an older build read back null (back-compat).
- StoreAndForwardService.EnqueueAsync gains optional executionId /
  sourceScript params, stamped onto the buffered message and surfaced on the
  CachedCallAttemptContext built in the retry loop.
- CachedCallAttemptContext gains ExecutionId / SourceScript.
- CachedCallLifecycleBridge.BuildPacket sets AuditEvent.ExecutionId and
  AuditEvent.SourceScript from the context (replacing the hard-coded
  SourceScript = null and its now-stale comment).
- IExternalSystemClient.CachedCallAsync / IDatabaseGateway.CachedWriteAsync
  gain optional executionId / sourceScript params; ScriptRuntimeContext's
  CachedCall / CachedWrite helpers pass _executionId / _sourceScript.

Script-side cached rows (CachedSubmit, immediate Attempted+Resolve) are
unchanged. All threading is additive — old buffered S&F rows still
deserialize and process with the new fields null.
This commit is contained in:
Joseph Doherty
2026-05-21 15:18:35 -04:00
parent 0149ce6180
commit 6f5a35f222
15 changed files with 505 additions and 40 deletions

View File

@@ -57,6 +57,20 @@ public interface ICachedCallLifecycleObserver
/// <param name="OccurredAtUtc">When this attempt completed.</param>
/// <param name="DurationMs">Duration of the attempt in milliseconds (null when not measured).</param>
/// <param name="SourceInstanceId">Originating instance, when known.</param>
/// <param name="ExecutionId">
/// Audit Log #23 (ExecutionId Task 4): the originating script execution's
/// per-run correlation id, threaded through the store-and-forward buffer from
/// the cached-call enqueue path. The audit bridge stamps it onto the
/// retry-loop <c>ApiCallCached</c>/<c>DbWriteCached</c> Attempted and
/// <c>CachedResolve</c> rows so they correlate with the rest of the run.
/// <c>null</c> for rows buffered before Task 4 (back-compat).
/// </param>
/// <param name="SourceScript">
/// Audit Log #23 (ExecutionId Task 4): the originating script identifier,
/// threaded alongside <paramref name="ExecutionId"/> so the retry-loop audit
/// rows carry the same <c>SourceScript</c> provenance the script-side cached
/// rows already do. <c>null</c> when not known.
/// </param>
public sealed record CachedCallAttemptContext(
TrackedOperationId TrackedOperationId,
string Channel,
@@ -69,7 +83,9 @@ public sealed record CachedCallAttemptContext(
DateTime CreatedAtUtc,
DateTime OccurredAtUtc,
int? DurationMs,
string? SourceInstanceId);
string? SourceInstanceId,
Guid? ExecutionId = null,
string? SourceScript = null);
/// <summary>
/// Coarse outcome of one cached-call delivery attempt, observed from inside