Portal Community

Structure

public sealed record NodeFieldDescriptor
{
    public string           FieldId          { get; init; } = string.Empty;
    public string           Description      { get; init; } = string.Empty;
    public ExpressionPolicy ExpressionPolicy { get; init; } = new();
    public DataFlowPolicy   DataFlowPolicy   { get; init; } = new();
    public HilPolicy        HilPolicy        { get; init; } = new();
    public SecurityPolicy   SecurityPolicy   { get; init; } = new();
}

In extension JSON the same structure appears as:

{
  "id": "to",
  "description": "Recipient email address",
  "expressionPolicy": { ... },
  "dataFlowPolicy": { ... },
  "hilPolicy": { ... },
  "securityPolicy": { ... }
}

FieldId

The FieldId is the canonical key used across the entire policy system. It must match the actual property name on the node's settings class.

Naming Conventions

PatternUse forExample
Simple camelCaseTop-level config propertiesto, subject, credentialId
Dot-notation pathNested object propertiesauth.credentialId, response.body, slack.ts
Case matters Use camelCase to match the C# settings property names. TimeoutSeconds (PascalCase) and timeout_seconds (snake_case) will both fail resolver lookup silently.

Why FieldId is critical

The FieldId is the key used by every platform subsystem:

Which Fields Need Descriptors?

ScenarioAdd descriptor?
Field receives data from upstream nodesYes
Field sends data to downstream nodesYes
Field should appear in the HIL inboxYes
Field contains a credential or secretYes — for masking
Field is evaluated from a template or expressionYes
Static internal routing fields (resource, operation)Optional
Private implementation detailsNo

Default Policy Values

If you create a NodeFieldDescriptor without specifying a policy, these defaults apply:

Policy PropertyDefault
ExpressionPolicy.EvaluationStageAtConfigLoad
ExpressionPolicy.EvaluatorKindTemplate
DataFlowPolicy.AcceptsUpstreamInputfalse
DataFlowPolicy.EmitsToDownstreamfalse
DataFlowPolicy.PersistsToMemoryfalse
DataFlowPolicy.ExcludeFromInputMappingfalse
DataFlowPolicy.ExcludeFromOutputMappingfalse
HilPolicy.SendToHilfalse
HilPolicy.DisplayModeConcealed
HilPolicy.InputModeLocked
SecurityPolicy.MaskInLogsfalse
SecurityPolicy.MaskInOutputfalse
SecurityPolicy.RequiresElevatedAccessfalse

The default posture is safe: fields are not shown to humans and not masked (because they contain no sensitive data by default). You explicitly opt in to each capability.

One Descriptor Per Field

// CORRECT
new NodeFieldDescriptor { FieldId = "to",      ... },
new NodeFieldDescriptor { FieldId = "subject",  ... },
new NodeFieldDescriptor { FieldId = "body",     ... }

// WRONG — do not describe multiple fields in one descriptor
new NodeFieldDescriptor { FieldId = "to, subject, body", ... }

Full Example

new NodeFieldDescriptor
{
    FieldId     = "body",
    Description = "Email body — supports template expressions and HTML.",

    ExpressionPolicy = new ExpressionPolicy
    {
        EvaluationStage = EvaluationStage.AtInputReady,
        EvaluatorKind   = EvaluatorKind.Template
    },

    DataFlowPolicy = new DataFlowPolicy
    {
        AcceptsUpstreamInput     = true,
        EmitsToDownstream        = false,
        PersistsToMemory         = false,
        ExcludeFromInputMapping  = false,
        ExcludeFromOutputMapping = true
    },

    HilPolicy = new HilPolicy
    {
        SendToHil   = true,
        DisplayMode = HilDisplayMode.ReadableContext,
        InputMode   = HilInputMode.RequiredFromHuman,
        Label       = "Email Body",
        Description = "Review and confirm the message body before it is sent."
    },

    SecurityPolicy = new SecurityPolicy
    {
        MaskInLogs             = false,
        MaskInOutput           = false,
        RequiresElevatedAccess = false
    }
}