Flow Studio
GuardRailExecutionContext
GuardRailExecutionContext is the object passed to IGuardRailExecutor.ExecuteAsync. It contains tenant and user identity, the operation identifier, input/output data, trace correlation ID, and arbitrary metadata.
GuardRailExecutionContext Shape
// BizFirst.Ai.GuardRails.Domain.Models.GuardRailExecutionContext
public class GuardRailExecutionContext
{
// Multi-tenancy isolation — use for scoped rate limits, quotas
public long TenantId { get; set; }
// The user who triggered the execution (0 if system/scheduled)
public long UserId { get; set; }
// The node's ProcessElementKey — unique identifier for this node in the workflow
public string OperationId { get; set; }
// Pre-phase: the input data arriving at this node
// Post-phase: the output data produced by the executor
public object? Input { get; set; }
public object? Output { get; set; }
// Additional metadata set by BaseNodeExecutor:
// "element_key" → ProcessElementKey
// "executor_type" → executor class name
public IDictionary<string, object?> Metadata { get; set; }
// Distributed trace ID for correlation (OpenTelemetry)
public string TraceId { get; set; }
// "Pre" or "Post" (set by the pipeline stage)
public string PhaseId { get; set; }
// When the context was created
public DateTime CreatedAt { get; set; }
}
How the Context Is Built
The context is built in BaseNodeExecutor.OnPreGuardRails from the ProcessElementExecutionContext:
var guardContext = new GuardRailExecutionContext
{
TenantId = (long)executionContext.TenantId,
UserId = (long)executionContext.UserId,
OperationId = executionContext.ProcessElementKey ?? "unknown",
Input = executionContext.InputData,
TraceId = "from-open-telemetry-activity",
PhaseId = "Pre",
Metadata = new Dictionary<string, object?>
{
{ "element_key", executionContext.ProcessElementKey },
{ "executor_type", this.GetType().Name }
}
};
Accessing Context in Your Guard
public async Task<GuardRailCheckResult> ExecuteAsync(
GuardRailExecutionContext context,
GuardRailPhase phase,
CancellationToken ct)
{
if (phase != GuardRailPhase.Pre)
return GuardRailCheckResult.Success();
// Scope rate limit per tenant
var tenantId = context.TenantId;
var nodeKey = context.OperationId;
// Access the input payload
var inputDict = context.Input as Dictionary<string, object>;
// Access custom metadata
var executorType = context.Metadata.TryGetValue("executor_type", out var et)
? et?.ToString() ?? "unknown"
: "unknown";
// Your check logic
var allowed = await CheckRateLimitAsync(tenantId, nodeKey, ct);
return allowed
? GuardRailCheckResult.Success()
: GuardRailCheckResult.Blocked("Rate limit exceeded", retryAfterSeconds: 30);
}
Post-Phase Output Access
In the Post phase, context.Output is populated with the executor's NodeExecutionResult.OutputData. Guards can inspect or even sanitize the output:
// Post-phase PII detection example
if (phase == GuardRailPhase.Post)
{
var output = context.Output as Dictionary<string, object>;
if (output != null && ContainsPii(output))
{
RedactPii(output); // mutate the dictionary in-place
return GuardRailCheckResult.Success(outputModified: true);
}
}