Portal Community
Node-level, not field-level Unlike the other four policies, SuspensionPolicy is set on the manifest itself (not on individual field descriptors) and applies to the entire node's wait behaviour.

Structure

public sealed class SuspensionPolicy
{
    public int             TimeoutSeconds               { get; init; } = 0;
    public string          TimeoutPortKey               { get; init; } = string.Empty;
    public TimeoutBehavior TimeoutBehavior              { get; init; } = TimeoutBehavior.AbsoluteDeadline;
    public int[]           ReminderIntervalSeconds      { get; init; } = Array.Empty<int>();
    public int             SlaThresholdSeconds          { get; init; } = 0;
    public bool            EmitSlaBreachEvent           { get; init; } = false;
    public bool            AllowAdminForceComplete      { get; init; } = false;
    public bool            CapturePreSuspensionSnapshot { get; init; } = false;
}

TimeoutSeconds

Maximum seconds the node may remain suspended before the timeout action triggers. 0 means no timeout (waits indefinitely).

DurationSeconds
1 hour3600
12 hours43200
24 hours86400
48 hours172800
7 days604800

TimeoutPortKey

The output port the node routes to when the timeout fires. Must be defined in the node's port configuration.

// Output ports
"approved"  → approval accepted path
"rejected"  → approval rejected path
"expired"   → timeout / escalation path  ← TimeoutPortKey points here

Common values: "expired", "timeout", "escalated".

TimeoutBehavior

AbsoluteDeadline

The timeout clock starts at the moment of suspension. The node has exactly TimeoutSeconds from suspension regardless of SLA configuration.

Use when: There is a hard business deadline that counts from task creation.

// Timeline
T+0h   Node suspends          ← Clock starts
T+24h  SLA breach event       (if slaThresholdSeconds = 86400)
T+48h  Timeout fires          ← TimeoutPortKey route

AfterSlaThreshold

The timeout clock starts after the SLA threshold has been breached. The actor has a grace period equal to SlaThresholdSeconds before the timeout countdown begins.

Use when: There is a soft SLA commitment and a hard cutoff only after the SLA is missed.

// Timeline (SlaThreshold = 12h, TimeoutSeconds = 24h)
T+0h   Node suspends
T+12h  SLA breach event       ← Countdown starts here
T+36h  Timeout fires          ← T+12h + 24h

ReminderIntervalSeconds

Array of intervals (in seconds) at which reminder notifications are sent to the assigned actor. Intervals are relative to when the countdown starts (depends on TimeoutBehavior). Set to an empty array for no reminders.

"reminderIntervalSeconds": [86400, 43200, 3600]
// Reminders at 24h, 12h, and 1h before timeout (AbsoluteDeadline)

"reminderIntervalSeconds": [43200, 7200]
// Reminders at 12h and 2h after SLA breach (AfterSlaThreshold)

SlaThresholdSeconds

Seconds after which the suspension is an SLA breach. Triggers EmitSlaBreachEvent and starts the AfterSlaThreshold countdown. Set to 0 for no SLA monitoring.

SLA vs Timeout: SLA = the commitment ("should be done in X hours"). Timeout = the hard cutoff ("if not done, we move on"). Typically SlaThresholdSeconds < TimeoutSeconds.

EmitSlaBreachEvent

When true, emits a domain event when SlaThresholdSeconds is exceeded. Consumed by monitoring dashboards, SLA compliance reports, and automatic escalation workflows.

AllowAdminForceComplete

Set to trueSet to false
Approval nodes — admin override is an acceptable escape hatch when a vote is stalled Data collection forms — actual human data is required; admin cannot substitute the submission

CapturePreSuspensionSnapshot

When true, captures a complete snapshot of workflow state immediately before suspension — for audit trail, replay capability, and debugging. Disable for high-volume workflows where storage cost matters.

Complete Examples

High-stakes approval — hard 48h deadline

suspensionPolicy: new SuspensionPolicy
{
    TimeoutSeconds               = 172800,   // 48 hours
    TimeoutPortKey               = "expired",
    TimeoutBehavior              = TimeoutBehavior.AbsoluteDeadline,
    ReminderIntervalSeconds      = new[] { 86400, 43200, 3600 },  // 24h, 12h, 1h before expiry
    SlaThresholdSeconds          = 86400,    // 24h SLA
    EmitSlaBreachEvent           = true,
    AllowAdminForceComplete      = true,
    CapturePreSuspensionSnapshot = true
}

Data collection form — SLA + grace period

suspensionPolicy: new SuspensionPolicy
{
    TimeoutSeconds               = 86400,    // 24h after SLA breach
    TimeoutPortKey               = "expired",
    TimeoutBehavior              = TimeoutBehavior.AfterSlaThreshold,
    ReminderIntervalSeconds      = new[] { 43200, 7200 },  // 12h and 2h after breach
    SlaThresholdSeconds          = 43200,    // 12h SLA
    EmitSlaBreachEvent           = true,
    AllowAdminForceComplete      = false,    // actual form data required
    CapturePreSuspensionSnapshot = true
}

No suspension (action node)

NodeExecutorManifest.From(
    ProcessElementTypeCode,
    fields: new[] { ... },
    suspensionPolicy: null   // executes immediately, no waiting
)

Suspension Infrastructure Services

ServiceRole
SuspensionPolicyOrchestratorReads the policy and registers all timers/events at suspension time
ISuspensionTimerServiceManages timeout and reminder timers
ISuspensionReminderServiceDelivers reminder notifications to actors
ISuspensionSlaMonitorTracks SLA thresholds and emits breach events

The node executor does not call these directly — it calls SuspendAsync() on BaseNodeExecutor, which triggers the orchestrator.