feat(inboundapi): mint inbound ExecutionId early, carry it as RouteToCallRequest.ParentExecutionId
This commit is contained in:
@@ -59,6 +59,18 @@ public sealed class AuditWriteMiddleware
|
||||
/// </summary>
|
||||
public const string AuditActorItemKey = "ScadaLink.InboundAPI.AuditActor";
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 (ParentExecutionId): <see cref="HttpContext.Items"/> key under
|
||||
/// which this middleware stashes the inbound request's per-request
|
||||
/// <c>ExecutionId</c> (a <see cref="Guid"/>) at the very start of the request.
|
||||
/// The id is minted ONCE and shared: the endpoint handler reads it to thread it
|
||||
/// onto a routed <c>RouteToCallRequest.ParentExecutionId</c>, and the
|
||||
/// middleware's own inbound audit row uses the same id for its
|
||||
/// <see cref="AuditEvent.ExecutionId"/>. Exposed as a constant so the handler
|
||||
/// and middleware share a single source of truth (no stringly-typed coupling).
|
||||
/// </summary>
|
||||
public const string InboundExecutionIdItemKey = "ScadaLink.InboundAPI.InboundExecutionId";
|
||||
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ICentralAuditWriter _auditWriter;
|
||||
private readonly ILogger<AuditWriteMiddleware> _logger;
|
||||
@@ -77,6 +89,17 @@ public sealed class AuditWriteMiddleware
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
// Audit Log #23 (ParentExecutionId): mint the inbound request's per-request
|
||||
// ExecutionId ONCE, here at the start of the request, and stash it on
|
||||
// HttpContext.Items. Two consumers share this single id:
|
||||
// (a) the endpoint handler reads it to thread onto a routed
|
||||
// RouteToCallRequest.ParentExecutionId, so a spawned site script
|
||||
// execution points back at this inbound request;
|
||||
// (b) the inbound audit row this middleware emits uses it as its own
|
||||
// ExecutionId (the row stays top-level — its ParentExecutionId is
|
||||
// never set).
|
||||
ctx.Items[InboundExecutionIdItemKey] = Guid.NewGuid();
|
||||
|
||||
// Buffer the request body up front so we can both audit it and let the
|
||||
// downstream handler still parse it. EnableBuffering swaps the request
|
||||
// stream for a seekable wrapper that the framework rewinds at the end
|
||||
@@ -145,17 +168,14 @@ public sealed class AuditWriteMiddleware
|
||||
OccurredAtUtc = DateTime.UtcNow,
|
||||
Channel = AuditChannel.ApiInbound,
|
||||
Kind = kind,
|
||||
// Audit Log #23: a fresh per-request execution id so the
|
||||
// inbound row carries a request identifier (closes the design
|
||||
// gap that inbound rows should be correlatable).
|
||||
//
|
||||
// This id is intentionally request-local: it is NOT bridged to
|
||||
// RouteHelper's routed-call correlation id or to
|
||||
// HttpContext.TraceIdentifier. Threading an inbound request's
|
||||
// execution id through to the routed script execution (so an
|
||||
// inbound call and the outbound API/DB rows it triggers share
|
||||
// one id) is a deliberate future follow-up, out of scope here.
|
||||
ExecutionId = Guid.NewGuid(),
|
||||
// Audit Log #23: the per-request execution id minted ONCE at the
|
||||
// start of the request (InvokeAsync) and stashed on
|
||||
// HttpContext.Items. The same id is threaded onto a routed
|
||||
// RouteToCallRequest.ParentExecutionId by the endpoint handler,
|
||||
// so an inbound request and the site script it routes to share
|
||||
// one correlation point. This inbound row stays top-level — its
|
||||
// own ParentExecutionId is never set (see below).
|
||||
ExecutionId = ResolveInboundExecutionId(ctx),
|
||||
// CorrelationId is purely the per-operation-lifecycle id; an
|
||||
// inbound request is a one-shot from the audit row's
|
||||
// perspective with no multi-row operation to correlate.
|
||||
@@ -225,6 +245,24 @@ public sealed class AuditWriteMiddleware
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 (ParentExecutionId): reads the inbound request's per-request
|
||||
/// <c>ExecutionId</c> that <see cref="InvokeAsync"/> minted and stashed on
|
||||
/// <see cref="HttpContext.Items"/> under <see cref="InboundExecutionIdItemKey"/>.
|
||||
/// Falls back to a fresh id only if the slot is somehow absent — the inbound
|
||||
/// audit row must always carry an execution id.
|
||||
/// </summary>
|
||||
private static Guid ResolveInboundExecutionId(HttpContext ctx)
|
||||
{
|
||||
if (ctx.Items.TryGetValue(InboundExecutionIdItemKey, out var stashed)
|
||||
&& stashed is Guid id)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
return Guid.NewGuid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the API key name the endpoint handler stashed on
|
||||
/// <see cref="HttpContext.Items"/> after successful auth. Falls back to
|
||||
|
||||
Reference in New Issue
Block a user