Summary#
Two per-slot settings order and condition a wait. After = [A, B] makes a slot open only once every named sibling
slot is satisfied — until then it sits Pending: visible, but not claimable and with no SLA clock running.
When = <predicate> decides whether the slot EXISTS at all, evaluated when the slot would open (not at state
entry) — so a value that changes mid-review (an expense amount raised past a threshold) correctly grows a slot that
did not exist before. Slots with neither open immediately, in parallel.
Signature#
subscribe <Event>(<typed params>) as <Alias> {
When = <predicate over this.Item>; // the slot only EXISTS while this holds
After = [<SiblingAlias>, <SiblingAlias>]; // …and only OPENS once all of these are satisfied
}Description#
After— a dependency, not a mode.Afternames sibling slots of the same state. A slot with predecessors is createdPendingat state entry and cannot be claimed or deposited into (a deposit is refused, like an out-of-pool one); no clock runs, so it can never breach for a predecessor's slowness. When the LAST predecessor is satisfied the slot opens —Unassigned(orAssignedif it declares anAssignee) — and its SLA clock starts.Whenis evaluated at OPEN time. For a slot withWhenandAfter, the condition is checked at the moment its predecessors complete, over the currentthis.Item. A slot whoseWhenis false is never created — notPending, not shown, not counted toward completion. If the condition only becomes true later (the amount was raised), the slot grows then. For a slot withWhenbut noAfter, the condition is evaluated at state entry.- Completion.
on Completefires when every slot that EXISTS AND IS OPEN is satisfied. AWhen-false (never created) slot does not block completion; aPendingor open-but-unsatisfied slot does. - Observing it —
SlotStatus+.Slots. TheSlotStatusenum (Pending,Unassigned,Assigned,Satisfied,Cancelled,Breached) is the type ofWf.For(entity).<Slot>.Status.Wf.For(entity).Slotsreturns the run's live slots (List<SlotView>, each with.Nameand.Status) — a slot theWhengate never created is simply absent, soWf.For(e).Slots.Any(s => s.Name == "Cfo")is false for an expense below the threshold.
Examples#
Manager and Finance approve in parallel; the CFO slot opens only after both, and only for large expenses:
enum Decision { Approve, Reject }
enum RequisitionStatus { Approvals, Approved, Rejected }
enum Role { Staff, Finance, Cfo }
enum Dept { Engineering, Finance, Legal }
// The entities the workflow reaches into. The example named all four and declared none — which the
// gate could not see while the fence was exempt from it.
[Principal]
entity Person {
[Required, MaxLength(200)] string Email;
Role Role = Role.Staff;
Dept Department = Dept.Engineering;
Person Manager;
security { allow read, create when IsAuthenticated; }
}
entity Requisition {
[Required] Person Employee;
decimal Cost;
RequisitionStatus Status; // no default: the workflow owns this field
security { allow read, create, update when IsAuthenticated; }
}
entity Approval {
[Required] Requisition Requisition;
Person By;
DateTime At;
security { allow read, create when IsAuthenticated; }
}
workflow RequisitionApproval {
Tracks = Requisition.Status;
Autostart = true;
Initial = Approvals;
event Approve(Decision decision);
state Approvals {
subscribe Approve(Decision decision) as Manager {
Assignee = this.Item.Employee.Manager;
}
subscribe Approve(Decision decision) as Finance {
Candidates = u => u.Department == Dept.Finance;
}
// Pending until BOTH predecessors are satisfied — and only exists for large expenses.
subscribe Approve(Decision decision) as Cfo {
When = this.Item.Cost > 10000;
After = [Manager, Finance];
Candidates = u => u.Role == Role.Cfo;
}
on Approve(Decision decision, Slot slot) {
new Approval { Requisition = this.Item, By = slot.Assignee, At = DurableClock.Now };
}
on Complete { goto Approved; }
}
terminal success Approved { }
terminal error Rejected { Message = "expense rejected"; }
}Observing the CFO slot's lifecycle from a test (or a UI):
Assert.Equal(SlotStatus.Pending, RequisitionApproval.For(e).Cfo.Status); // visible, not open
runas (Mia) { RequisitionApproval.For(e).Manager.Approve(Decision.Approve); }
Assert.Equal(SlotStatus.Pending, RequisitionApproval.For(e).Cfo.Status); // one predecessor down
runas (Otto) { RequisitionApproval.For(e).Finance.Approve(Decision.Approve); }
Assert.Equal(SlotStatus.Unassigned, RequisitionApproval.For(e).Cfo.Status); // NOW it opens — clock starts
// a small expense never grows a CFO slot at all
Assert.False(RequisitionApproval.For(small).Slots.Any(s => s.Name == "Cfo"));See also#
- subscribe — the wait these settings condition
- Requires — named preconditions, and the live checklist — the other completion gate (a quorum predicate)
- <span class="planned" title="this page is planned and not written yet">workflow-route</span> — the event-keyed arm shared by the sibling slots (and its
Slot slotparam) - fan-out (foreach subscribe) — many parallel slots from one declaration
- <span class="planned" title="this page is planned and not written yet">workflow-state</span> — the enclosing state