Portal Community

EvaluationContext Shape

// BizFirst.Runtime.Expressions.Evaluation.Domain.Models.EvaluationContext
public class EvaluationContext
{
    // Identity
    public string  TenantId      { get; set; }
    public string? UserId        { get; set; }
    public string? ApplicationId { get; set; }
    public string? CorrelationId { get; set; }

    // Expression syntax configuration
    public WildcardSyntax ActiveSyntax { get; set; }  // AtBrace = @{...}

    // Data sources — each is an adapter over the ProcessEngine domain
    public IExecutionMemory?  Memory         { get; set; }  // variables
    public INodeDataContext?  NodeData       { get; set; }  // input/output data
    public INodeExecutionFacts? ExecutionFacts { get; set; }  // node timing, status
    public IWorkflowFlowFacts?  FlowFacts     { get; set; }  // thread, execution ids
}

How ExpressionContextFactory Builds It

// ExpressionContextFactory.cs
public EvaluationContext Create(ProcessElementExecutionContext elementContext)
{
    var thread  = elementContext.ParentThreadContext;
    var memory  = thread?.Memory;

    return new EvaluationContext
    {
        TenantId      = elementContext.TenantId?.ToString() ?? string.Empty,
        UserId        = elementContext.UserId?.ToString(),
        ActiveSyntax  = WildcardSyntax.AtBrace,   // @{...} syntax
        CorrelationId = thread?.ParentProcessContext?.CorrelationId,

        // ExecutionMemoryAdapter bridges domain ExecutionMemory → IExecutionMemory
        Memory        = memory is not null ? new ExecutionMemoryAdapter(memory) : null,

        // NodeDataContextAdapter exposes input and node outputs
        NodeData      = new NodeDataContextAdapter(elementContext),

        // NodeExecutionFactsAdapter exposes timing/status facts
        ExecutionFacts = new NodeExecutionFactsAdapter(elementContext),

        // WorkflowFlowFactsAdapter exposes execution/thread IDs
        FlowFacts     = thread is not null ? new WorkflowFlowFactsAdapter(thread) : null,
    };
}

Adapters and What They Expose

AdapterExposesDirective Prefix
ExecutionMemoryAdapter Workflow variables from ExecutionMemory.GetVariable(key) @{var:name}
NodeDataContextAdapter Current node's input data + all completed node outputs via NodeOutputs @{input:field}, @{output:nodeId.field}
NodeExecutionFactsAdapter Node timing, status, retry count from ProcessElementExecutionContext @{facts:...}
WorkflowFlowFactsAdapter ExecutionId, ThreadId, ParentProcessId from the thread context @{context:executionId} etc.

When Context Is Partial

If the node has no parent thread context (e.g. in unit tests or trigger-only validation), the adapters for Memory and FlowFacts will be null. The resolver treats null adapters as empty data sources — expressions referencing variables or execution IDs resolve to null/empty rather than throwing.

NodeDataContextAdapter Is Branch-Aware GetNodeResultData(nodeId) calls GetNodeResultBranches(nodeId) on the execution memory, which returns the correct branch output (success data, not error data) for each completed node. This ensures @{output:nodeId.field} always reads from the success output of that node.