Core Type
Field Descriptors
A NodeFieldDescriptor is the building block of every manifest — it describes
one field of the node and bundles together all four per-field policies that govern its
behaviour.
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
| Pattern | Use for | Example |
|---|---|---|
| Simple camelCase | Top-level config properties | to, subject, credentialId |
| Dot-notation path | Nested object properties | auth.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:
- DataFlowPolicy — matches upstream output fields to this node's input
- SecurityPolicy — identifies which field to mask in logs
- HilPolicy — renders the correct form field in the HIL inbox
- ExpressionPolicy — tells the evaluator which config property to process
Which Fields Need Descriptors?
| Scenario | Add descriptor? |
|---|---|
| Field receives data from upstream nodes | Yes |
| Field sends data to downstream nodes | Yes |
| Field should appear in the HIL inbox | Yes |
| Field contains a credential or secret | Yes — for masking |
| Field is evaluated from a template or expression | Yes |
Static internal routing fields (resource, operation) | Optional |
| Private implementation details | No |
Default Policy Values
If you create a NodeFieldDescriptor without specifying a policy, these defaults apply:
| Policy Property | Default |
|---|---|
ExpressionPolicy.EvaluationStage | AtConfigLoad |
ExpressionPolicy.EvaluatorKind | Template |
DataFlowPolicy.AcceptsUpstreamInput | false |
DataFlowPolicy.EmitsToDownstream | false |
DataFlowPolicy.PersistsToMemory | false |
DataFlowPolicy.ExcludeFromInputMapping | false |
DataFlowPolicy.ExcludeFromOutputMapping | false |
HilPolicy.SendToHil | false |
HilPolicy.DisplayMode | Concealed |
HilPolicy.InputMode | Locked |
SecurityPolicy.MaskInLogs | false |
SecurityPolicy.MaskInOutput | false |
SecurityPolicy.RequiresElevatedAccess | false |
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
}
}