Summary#
Every other way of reading a workflow starts from one row: what is the state of this invoice? An inbox asks the opposite question — what is waiting for me? — and it crosses every run.
Workflow.Inbox<T>() answers it for whoever is asking right now.
Signature#
Workflow.Inbox<TrackedEntity>()No arguments: it is the CURRENT principal's queue. Filter, order and count it like any other list.
Description#
What is in it#
A slot is in your queue when it is assigned to you, or it is open and you satisfy its Candidates. Slots
that are waiting on something else are included too, carrying their status — so a screen can grey out "CFO, waiting
for Manager" rather than hiding work that is coming.
T is the entity a workflow Tracks. Asking for an entity no workflow drives is a compile error, because a
queue is a thing a workflow produces.
The row#
| member | what it is |
|---|---|
Item | the tracked entity, typed — an Invoice, not an id |
SlotAlias | which slot is asking |
WorkflowName | the workflow it belongs to |
Status | the slot's live status |
OpenedAt / BreachesAt | when it arrived, and when it runs out of time |
RunId / SlotId | the run and the slot themselves |
The alias is not decoration. "Waiting on you as their manager" and "waiting on you as finance" are different asks, and the same person can hold both on different items. A queue that could not tell them apart would be showing one list where there are two.
BreachesAt is the sort key. For a workflow that rejects on breach, "3 hours left" is the most actionable
thing on the row. It is null when a slot has no deadline — absence, not a far-future date to filter around.
OpenedAt is when the slot opened, not when the run reached the state. For a slot held closed by
After those are different moments — it waits Pending while its predecessors run —
and the row reports the later one. So "waiting since" means waiting on you, and it agrees with BreachesAt,
whose clock starts at the same moment.
Reading through Item#
Item is the tracked entity, so a screen reads it directly — that is the point of the queue being typed rather than
a list of ids. Navigating it needs it included, as any reference does:
enum ClaimStage { Filed, Approved, Rejected }
[Principal] entity Employee {
[Required] [MaxLength(80)] string DisplayName;
security {
allow read when IsAuthenticated; // a principal row is not public — say who may read it
allow create when IsAuthenticated;
}
}
entity Invoice {
[Required] [MaxLength(120)] string Title;
[Required] decimal Amount;
[Required] Employee Owner;
ClaimStage Stage;
security {
allow read, update when IsAuthenticated;
allow create when IsAuthenticated;
}
}
workflow ExpenseApproval {
Tracks = Invoice.Stage;
Autostart = true;
Initial = Filed;
event Decide(bool approved);
state Filed {
subscribe Decide(bool approved) as Manager { Assignee = this.Item.Owner; }
on Manager(bool approved) {
when (approved) { goto Approved; }
default { goto Rejected; }
}
}
terminal success Approved { }
terminal error Rejected { Message = "rejected"; }
}
int WaitingOnMe() {
return Workflow.Inbox<Invoice>()
.Include(r => r.Item.Owner)
.OrderBy(r => r.BreachesAt)
.Count();
}Whoever is asking#
The queue is defined against the current principal, and it follows runas. That is deliberate: a workflow can
put an agent in a slot as readily as a person, so "what is waiting on this principal" stays one question rather
than growing a second surface for the non-human case.
An anonymous caller has an empty queue — nothing is waiting on nobody. That is an answer, not an error, so a public page still renders.
It is live#
The queue is re-read, not remembered. Act on a slot and it leaves your queue; it does not linger as a row that does nothing when clicked. One person acting does not change anyone else's queue.
Answering a row#
Workflow.Deposit(row, Decide(true)) answers the slot a row is, and Workflow.Claim(row) /
Workflow.Release(row) take and hand back unassigned work. The event is named at the call site because a queue's rows
are heterogeneous — which slot a row turned out to be is known only once the queue has been read.
Examples#
Only the work of one kind, most urgent first:
Workflow.Inbox<Invoice>()
.Include(r => r.Item)
.Where(r => r.SlotAlias == "Finance")
.OrderBy(r => r.BreachesAt)Notes#
Authorization is the workflow's, not the queue's. Membership is decided by the same Candidates evaluation that
governs claiming and depositing — the queue does not get its own rules. A queue is exactly the place a second,
looser answer would otherwise appear, and there is deliberately nowhere for one to live.
It runs on the server. Deciding what a principal may act on is not a question a client can be trusted to answer about itself.
See also#
- Workflow.WorkByItem<T> (one row per item — the board read) — one row per ITEM rather than per slot: the BOARD read, with the clock that governs across an item's slots
- Tracks and Initial (the field a workflow drives) — the
Tracksdeclaration that makes a queue exist at all - Candidates (slot) — who is eligible for a slot
- Include (pre-loading relations) — loading
Itembefore you read through it