Portal Community

The Problem Without Node Policies

Without a contract, every node is a black box. The platform cannot:

Node Policies solve this by giving every node a machine-readable manifest that the platform reads to handle all of these concerns automatically.

The Five Policy Types

PolicyControls
ExpressionPolicyWhen and how a field's value is computed (evaluation stage + evaluator kind)
DataFlowPolicyWhich fields accept upstream data, emit to downstream, and persist to workflow memory
HilPolicyWhat humans see in their task inbox, how it is displayed, and what they can change
SecurityPolicyField masking in logs and output, and elevated-access gates
SuspensionPolicyTimeout, SLA thresholds, reminders, and escalation for nodes that wait

The Two Layers

Layer 1 — Code Manifest (Compile-time)

Each node executor defines its manifest in a .Config.cs partial by overriding GetNodeExecutorManifest():

// SmtpNodeExecutor.Config.cs
protected override NodeExecutorManifest? GetNodeExecutorManifest()
    => NodeExecutorManifest.From(
        ProcessElementTypeCode,
        new[]
        {
            new NodeFieldDescriptor
            {
                FieldId          = "to",
                Description      = "Recipient email address",
                ExpressionPolicy = new ExpressionPolicy
                {
                    EvaluationStage = EvaluationStage.AtInputReady,
                    EvaluatorKind   = EvaluatorKind.Template
                },
                DataFlowPolicy = new DataFlowPolicy
                {
                    AcceptsUpstreamInput     = true,
                    ExcludeFromOutputMapping = true
                },
                HilPolicy = new HilPolicy
                {
                    SendToHil   = true,
                    DisplayMode = HilDisplayMode.ReadableContext,
                    InputMode   = HilInputMode.EditableOptional,
                    Label       = "To"
                },
                SecurityPolicy = new SecurityPolicy()
            }
        }
    );

Layer 2 — Extension JSON (Runtime Override)

Administrators store additional field descriptors or override existing ones in the database (ProcessNodePolicies table). This JSON is merged with the code manifest at runtime by NodeFieldManifestResolver, allowing customisation without redeployment — for example adjusting HIL labels or enabling masking for a specific tenant.

{
  "nodeTypeName": "email-smtp",
  "fields": [
    {
      "id": "body",
      "hilPolicy": {
        "sendToHil": true,
        "inputMode": "RequiredFromHuman",
        "label": "Email Body",
        "description": "You must confirm this before the email sends."
      }
    }
  ]
}

Runtime Execution Flow

1

Manifest registered at startup

GetNodeExecutorManifest() is called once per node type. The result is stored in NodeFieldManifestRegistry keyed by ProcessElementTypeCode.

2

Resolver merges code + DB overrides

NodeFieldManifestResolver.GetManifest(nodeTypeName, extensionJson) produces the final merged manifest for the execution.

3

Expression evaluation — AtConfigLoad stage

Fields with EvaluationStage.AtConfigLoad are resolved using the configured evaluator (Template, Literal, etc.).

4

DataFlow — input mapping

Fields with AcceptsUpstreamInput: true are populated from the upstream node's output data bag. Fields with ExcludeFromInputMapping: true are skipped.

5

Node executes

The executor runs its logic. Output fields are populated by the executor and held in the result.

6

DataFlow — output mapping

Fields with EmitsToDownstream: true are written to the downstream data bag. Fields with PersistsToMemory: true are written to workflow memory.

7

Security masking

Fields with MaskInLogs: true or MaskInOutput: true are redacted before logs are written and before the output bag leaves the node.

8

Suspension timers registered (if node suspends)

SuspensionPolicyOrchestrator reads the SuspensionPolicy and registers timeout and reminder timers via ISuspensionTimerService.

9

HIL fields sent to inbox (if in review mode)

Fields with SendToHil: true are formatted and routed to the actor's inbox. DisplayMode and InputMode control rendering.

Key Types

TypeRoleProject
NodeExecutorManifestContainer for all field descriptors + suspension policy for one node typeBizFirst.Ai.ProcessNodePolicies.Domain
NodeFieldDescriptorOne field and its four per-field policiesSame
ExpressionPolicyEvaluation stage + evaluator kindSame
DataFlowPolicyData routing rulesSame
HilPolicyHuman inbox rendering rulesSame
SecurityPolicyMasking and access rulesSame
SuspensionPolicyTimeout and reminder configurationSame
NodeFieldManifestRegistryThread-safe ConcurrentDictionary of code manifestsBizFirst.Ai.ProcessNodePolicies.Service
NodeFieldManifestResolverMerges code manifest with DB extension JSONSame
SuspensionPolicyOrchestratorReads SuspensionPolicy, registers all timers at suspension timeBizFirst.Ai.ProcessEngine.Service
Type alias The old name NodeFieldManifest is aliased to NodeExecutorManifest in the GlobalUsings files:
global using NodeFieldManifest = BizFirst.Ai.ProcessNodePolicies.Domain.Models.NodeExecutorManifest;