Flow Studio
The Guard Pipeline
Guard rails are woven into the node execution pipeline at two points: PreGuardRails (stage 250) before the executor runs, and PostGuardRails (stage 550) after output is produced. A Blocked result in Pre short-circuits execution — the executor is never called.
Full Pipeline with Guard Stages
| Stage | Ordinal | What Happens |
|---|---|---|
| Entry | 100 | Load node definition |
| EntryValidate | 200 | Parse config, resolve expressions, populate settings |
| PreGuardRails | 250 | All Pre-phase guards run. Blocked → execution stops, result = GuardrailsViolation |
| PreProcess | 300 | HIL suspension check |
| Process | 400 | ExecuteInternalAsync (your code) |
| PostProcess | 500 | Post-execution hooks |
| PostGuardRails | 550 | All Post-phase guards run. Blocked → result overridden to failure |
| Exit | 600 | Persist result, emit SignalR event |
Pre-Execution Guard Flow
// BaseNodeExecutor.PreGuardRails.cs (simplified)
public virtual async Task<bool> OnPreGuardRails(
NodeExecutionContext ctx,
CancellationToken ct)
{
if (_guardExecutor == null) return true; // no guards registered
var guardContext = new GuardRailExecutionContext
{
TenantId = (long)elementContext.TenantId,
UserId = (long)elementContext.UserId,
OperationId = elementContext.ProcessElementKey,
Input = elementContext.InputData,
PhaseId = "Pre"
};
// Run all registered Pre-phase guards
var result = await _guardExecutor.ExecutePreAsync(guardContext, ct);
if (!result.IsAllowed)
{
// Short-circuit: set result in runtime info, skip executor
ctx.RuntimeInfo.ResultInfo.Result = new NodeExecutionResult
{
IsSuccess = false,
ErrorMessage = "Guardrails check failed",
OutputPortKey = "GuardrailsViolation"
};
return false;
}
return true;
}
Pre vs Post Phase Responsibilities
| Phase | Guards Run Here | Can Stop Execution |
|---|---|---|
| Pre | Rate limiting, input validation, circuit breaker, quota check | Yes — Blocked result prevents executor from running |
| Post | PII detection, content policy, output schema validation | Yes — Blocked result overrides the executor's result with a failure |
| Error | Error enrichment, fallback logic | No — runs for information/logging only |
Guard Ordering
Guards execute in the order they are registered in DI (first-registered-first-run for same phase). The first guard to return Blocked stops the pipeline — remaining guards are not evaluated. Ensure high-priority guards (quota, rate limit) are registered before lower-priority ones (content policy).
Fail-Open Exception Handling
If a guard throws an unhandled exception, the code logs a warning and continues (
return true). This fail-open design ensures that a broken custom guard does not bring down all workflow executions. Only explicit Blocked results halt execution.