# Workflow.Work&lt;T&gt; (everything outstanding) and its SLA numbers

> Every live slot of every run tracking T, whoever holds it — the unfiltered sibling of Workflow.Inbox. Rows carry Budget, Elapsed and Remaining for the deadline that governs them, so a screen can say "1h32m of the 4h for first response". One row type serves every viewpoint: an operator reads it whole, a requester filters to their own items, a holder filters on Assignee.

<!-- id: workflow-work · area: workflow · stability: stable · html: https://osysharp.com/reference/workflow/work/ -->

## Summary        {#summary}
[`Workflow.Inbox<T>()`](https://osysharp.com/reference/workflow/inbox/) answers *what is waiting for me*. **`Workflow.Work<T>()`** answers *what is
outstanding* — every live slot of every run tracking `T`, whoever holds it.

They return the same row, and that is the point: an operator, the person who raised the item, and the pool that can
pick it up all want the same facts, filtered differently.

## Signature      {#signature}
```osy syntax
Workflow.Work<TrackedEntity>()
```
No arguments. Filter, order and count it like any other list.

## Description    {#description}

### How do I get MY queue out of it?   {#viewpoints}
The inbox has a viewpoint built in — assigned to you, or you satisfy the slot's
[`Candidates`](https://osysharp.com/reference/workflow/candidates/). That is what stops it generalising: *"where has my expense report got to"* is
asked by someone who often cannot act on it at all. So the primitive is the unfiltered list, and each viewpoint is an
ordinary `.Where(…)`:

```osy syntax
Workflow.Work<Expense>()                                        // an operations board — everything
Workflow.Work<Expense>().Where(r => r.Assignee == me)           // what I am holding
Workflow.Work<Expense>().Where(r => r.SlaKind == SlaKind.Assigned && r.Remaining < TimeSpan.Zero)  // late to respond

Workflow.Work<Expense>()                                        // my submissions, wherever they are
  .Include(r => r.Item).Include(r => r.Item.Requester)          // …filtering THROUGH Item needs it loaded
  .Where(r => r.Item.Requester == me)
```

⚠ **Filtering through `Item` needs `Item` [included](https://osysharp.com/reference/query/include/).** The rows are already materialised, so a
`.Where(…)` over them runs in memory — an un-included reference has nothing to resolve. Include the hop you filter
on *and* `Item` itself. Fields on the row (`Assignee`, `SlaKind`, `Remaining`) need nothing.

**Security is not what changed.** Dropping the inbox filter drops a *relevance* question, not a permission one:
whether you may see an item at all is decided by that entity's own declared read rules, on the same rows, either way.
A requester who can only read their own expenses sees only their own — with no filter written.

### Which SLA numbers does a row carry?   {#sla}
| member | what it is |
|---|---|
| `Budget` | the SLA's total allowance — the *4h* |
| `Elapsed` | how much is gone — the *1h32m* |
| `Remaining` | `Budget - Elapsed`, **negative** once breached |
| `SlaKind` | which deadline these describe — `Assigned` (first response) or `Finished` (completion) |
| `BreachesAt` | when it runs out |

```osy syntax
foreach (var r in Workflow.Work<Ticket>().OrderBy(r => r.Remaining)) {
  Log.Information($"{r.Item.Title}: {r.Elapsed} of {r.Budget} ({r.SlaKind})");
}
```

**`SlaKind` is not decoration.** A slot can carry both an `Assigned` and a `Finished`
[milestone](https://osysharp.com/reference/workflow/milestone/) — *pick it up within 4h* and *close it within 24h* are different promises. The row
describes the one that **breaches soonest**, which is the same clock `BreachesAt` reports, so a row is always about
one deadline rather than a blend of two. Without `SlaKind`, "1h32m of 4h" would not say which promise it measures.

**`Remaining` goes negative on purpose.** *How far past* is the thing an operator is looking for, and clamping at
zero would flatten the worst rows into the merely-due ones.

**All five are null together** when no clock governs the slot — absence, not a zero that would sort as though the
budget were spent.

### Elapsed is accrued, not wall-clock   {#accrual}
Under [`ServiceHours`](https://osysharp.com/reference/workflow/service-hours/) an SLA only advances during business hours. `Elapsed` counts the same
way, so a ticket raised on Friday afternoon does not burn its budget over the weekend — and `Elapsed`, `Remaining`
and `BreachesAt` on one row always agree with each other. A clock declared `Accrues = false` measures real time, and
its `Elapsed` follows it.

This is also why the numbers come from here rather than being computed in app code: the answer depends on the
schedule the run is governed by, which is not a subtraction anyone can do from the outside.

## Examples       {#examples}
A support queue with a 4h first-response SLA and a 24h close, and the two reads an operations screen makes of it.

```osy title="an operations board, worst first" test app=workflow-work
enum TicketStage { Open, Working, Closed }

[Principal] entity Agent {
  [Required] [MaxLength(80)] string DisplayName;
  [Required] [MaxLength(40)] string Team;
  security {
    allow read   when IsAuthenticated;
    allow create when IsAuthenticated;
  }
}

entity Ticket {
  [Required] [MaxLength(120)] string Title;
  [Required] Agent Reporter;
  TicketStage Stage;
  security {
    allow read, update when IsAuthenticated;
    allow create       when IsAuthenticated;
  }
}

workflow TicketFlow {
  Tracks    = Ticket.Stage;
  Autostart = true;
  Initial   = Open;

  event Pick();
  event Resolve(bool fixed);

  state Open {
    subscribe Pick();
    on Pick { goto Working; }
  }

  state Working {
    subscribe Resolve(bool fixed) as Support {
      Candidates = u => u.Team == "Support";
      Assigned { Within = TimeSpan.FromHours(4);  }   // first response
      Finished { Within = TimeSpan.FromHours(24); }   // close
    }
    on Support(bool fixed) { goto Closed; }
  }

  terminal success Closed { }
}

// Everything past its deadline, worst first — the operator's screen.
int OverdueCount() {
  return Workflow.Work<Ticket>()
                 .Where(r => r.Remaining < TimeSpan.Zero)
                 .Count();
}

// The same read, one viewpoint narrower: where my own tickets have got to. Both Includes are needed — the filter
// navigates Item AND Item.Reporter, and an in-memory Where cannot resolve a reference that was never loaded.
int MySubmissions(Agent me) {
  return Workflow.Work<Ticket>()
                 .Include(r => r.Item)
                 .Include(r => r.Item.Reporter)
                 .Where(r => r.Item.Reporter == me)
                 .Count();
}
```

## See also       {#see-also}
- [Workflow.WorkByItem&lt;T&gt; (one row per item — the board read)](https://osysharp.com/reference/workflow/work-by-item/) — one row per ITEM rather than per slot: the BOARD read, with the clock that governs across an item's slots
- [Workflow.Inbox&lt;T&gt; (what is waiting for me)](https://osysharp.com/reference/workflow/inbox/) — the same row, filtered to the asking principal
- [Candidates (slot)](https://osysharp.com/reference/workflow/candidates/) — an operations board shows everyone's work, so use `<Wf>.For(r.Item).<Slot>.Candidates(u)`
  to decide which rows the VIEWER can act on
- [Assigned / Finished (milestones)](https://osysharp.com/reference/workflow/milestone/) — where `Assigned` / `Finished` budgets are declared
- [ServiceHours (SLA-accrual windows)](https://osysharp.com/reference/workflow/service-hours/) — what makes `Elapsed` business hours
- [Workflow.Retarget (re-base the SLA clocks)](https://osysharp.com/reference/workflow/retarget/) — changing an SLA budget mid-run
