Osy#betaRun it on Osyrin: osyrin.com

Reference / Workflow

subscribe

subscribe Event(typed params) [as Alias] { Candidates = …; Reassign = …; Assignee = …; When = …; After = […]; }

Declares that a workflow state waits on an event, and configures the wait — who may hold it, who may hand it on, whether it is armed at all, and what must be satisfied first. A subscribe holds CONFIG only: the routes that fire when the event arrives live at the state level, and the SLA promises live in its own Assigned/Finished blocks.

stableworkflowauthoring

Summary#

subscribe declares that a workflow state is listening for an event, and configures that wait — who may fill it, when it is armed, what must hold first, and what it promises. It is config only: the guarded routes that fire when the event arrives (on Event when (…) { … goto … }) live at the state level alongside the wait, not inside the subscribe block.

Signature#

subscribe <Event>(<typed params>) [as <Alias>] [foreach (<T> <x> in <collection>)] {
  Candidates = <principal => bool> | <() => List<Principal>>;   // who may HOLD or satisfy it
  Reassign   = <actor => bool>;                                 // who may hand it ON
  Assignee   = <expr>;                                          // who it is handed to at arm time
  When       = <predicate>;                                     // whether it is armed on this run at all
  After      = [<Slot>, …];                                     // slots that must be satisfied first

  Assigned { … }   // the pickup promise — Within, Remind, a breach arm
  Finished { … }   // the completion promise
  Requires { … }   // named preconditions on filling it
}

subscribe <Event>();   // bodyless — a wait with no configuration

Description#

A workflow state advances when an event it subscribes to arrives and a matching route transitions it. subscribe names that event and restates its typed parameters (so the payload is visible at the handler), then configures the wait itself. Each setting has its own page — see See also — and the shape to hold is that they answer different questions:

settingquestion
Candidateswho may hold or satisfy this slot
Reassignwho besides the holder may move it
Assigneewho it belongs to from the moment it is armed (a slot with one is not a pool slot)
Whenwhether this run gets the slot at all
Afterwhich sibling slots must be satisfied before it opens

The promises — how long a slot may sit unclaimed, how long its holder has, the nudges along the way, the retries, and where a missed one routes — are declared in the Assigned/Finished blocks inside the slot, not as flat settings on it. That is where Within, Remind, Retries, Backoff and the Unassigned / Unfinished / Exhausted arms live.

A slot's success routes (on <Event> when (…), on <Alias>(…)) and the state's own timers (on Expire, on Deadline) live at the state level, not inside the subscribe — every on … in one place.

An entity-typed parameter is a live row, not a copy. A route may read it and write through it, exactly as it can through the workflow's own tracked item, and those writes are saved with the rest of the transition:

event CustomerWithdrawsItem(OrderItem item);

on CustomerWithdrawsItem(OrderItem item) {
  item.Status = ItemStatus.Withdrawn;    // persisted with the transition
  RefundItem(item);
}

The event's delivery context (who deposited, the claim) is available to a route that opts into a Deposit parameter — see on — a route and its arms; it is never ambient.

A state may hold more than one subscribe (multi-wait / N-of-M); that is its own surface — see <span class="planned" title="this page is planned and not written yet">workflow-multiwait</span>.

Examples#

A pool slot the support team may take, a supervisor may move, and which promises to be picked up within an hour:

subscribe Respond() as Reply {
  Candidates = u => u.Team == Team.Support && u.OnDuty;
  Reassign   = a => a.IsSupervisor;

  Assigned { Within = TimeSpan.FromHours(1); Unassigned { goto Escalated; } }
}

The same wait as an app writes it, in context:

  state Draft {
    subscribe Submit();
    on Submit { goto Review; }
  }

Minimal — a wait with no config, just the event:

subscribe Submit();

⚠ Minimal is not optional. Adding a subscribe to a state adds a wait the state must get through, and on a state that ends with on Complete — the default "every armed slot is satisfied" test — that means the state now needs this one too. The line reads like a side channel and behaves like a co-requisite: answer the approval and the run sits where it is, with no error, no timeout and nothing to inspect, until somebody deposits this event as well. A slot declaring no Candidates is also open to everybody, so it goes on being offered in Workflow.Inbox<T>() to every principal for as long as it waits. osy lint reports the shape as workflow-unowned-slot-gates-completion. If a state should end on one particular wait, route that wait (on <Slot> { goto <State>; }) instead of leaving it to on Complete; if it ends on a condition over your own data, say so with complete when or a state-level Requires, both of which REPLACE the all-slots test.

See also#

Related

on — a route and its arms

An `on` route is what a state does when an event arrives: a flat body, or guarded `when` arms tried top to bottom with…

Candidates (slot)

Declares WHO may hold or satisfy a `subscribe` slot. `Candidates` is one expression surface that dispatches on its…

Assign — handing a slot to a named colleague

Give a slot to somebody else. Claiming takes work for yourself and releasing puts it back in the pool; assigning is the…

Assigned / Finished (milestones)

A milestone puts an SLA on a slot's progress — Assigned (someone must PICK IT UP within Within) and Finished (it must…

Tracks and Initial (the field a workflow drives)

Names the enum field a workflow owns and the state a run starts in. No application code may write that field, and when…