# Acting on an inbox row (deposit, claim, release)

> Answer a queued slot from the row itself. The event is named at the call site because a queue's rows are heterogeneous — which slot a row is, and so which verb it takes, is known only when the queue is read.

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

## Summary        {#summary}
[`Workflow.Inbox<T>()`](https://osysharp.com/reference/workflow/inbox/) tells a person what is waiting for them. These three verbs are how they
answer it — from the row, without knowing in advance which slot it turned out to be.

## Signature      {#signature}
```osy syntax
Workflow.Deposit(row, <Event>(args…))   // answer the slot this row is
Workflow.Claim(row)                      // take an unassigned slot you are eligible for
Workflow.Release(row)                    // hand a claimed slot back to the pool
```

## Description    {#description}
### Why the event is named, and not called   {#naming}
Everywhere else a deposit is spelled `<Workflow>.For(entity).<Slot>.<Event>(…)` — every part of it written by the
author. A queue does not work that way. One row may be a manager's decision and the next a finance sign-off, so
`row.Approve(…)` cannot exist: the verb set differs per workflow, and a given row's verb is only known once the queue
has been read.

So the event is an **argument**, the same shape [Workflow.Run (start a workflow)](https://osysharp.com/reference/workflow/run/) uses when you raise one by name
(`OrderFlow.RaisePayment(order, 100)`) — and the split in checking follows from that:

| checked by | what it checks |
|---|---|
| the compiler | `Event` is declared by a workflow that `Tracks` `T`, and its arguments type-check |
| the run | this row's slot is actually waiting for that event — and `Candidates`, `Requires` and quorum |

Naming an event the row is not waiting for is refused, not deposited. That matters more than it sounds: arguments
bind to the arm **by name**, so a wrong event's payload would otherwise arrive as parameters nobody set.

### It is the same deposit   {#same-deposit}
There is one deposit in the platform and this is it. A row does not carry permission — obtaining it changes nothing
about who may act. A principal holding someone else's row and naming the right event is still refused by the slot's
own `Candidates`, because the queue has no rules of its own and deliberately nowhere to keep any.

### Claiming   {#claiming}
The queue shows unassigned work you are eligible for, so `Workflow.Claim(row)` takes it and `Workflow.Release(row)`
gives it back. Both need a principal — nothing can hold a slot on nobody's behalf. Only the current holder may
release.

To hand a slot to a **named colleague** rather than back to the pool, there is a third verb —
[`Workflow.Assign(row, principal)`](https://osysharp.com/reference/workflow/assign/). It is separated out because it is the one act here that asks
about the actor as well as the target: eligibility to hold work is not authority to move it.

## Examples       {#examples}
The morning screen, and the button on it:

```osy title="approve from the queue" test app=workflow-inbox-act
enum ClaimStage { Filed, Approved, Rejected }

[Principal] entity Employee {
  [Required] [MaxLength(80)] string DisplayName;
  security {
    allow read   when IsAuthenticated;
    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"; }
}

void ApproveOldest() {
  var oldest = Workflow.Inbox<Invoice>()
                       .OrderBy(r => r.OpenedAt)
                       .First();
  Workflow.Deposit(oldest, Decide(approved: true));
}
```

Once it is answered the row leaves the queue, because the queue is re-read rather than remembered.

## Notes          {#notes}
**A fanned-out row acts on its own slot.** A row addresses the slot it is a row for, so one principal holding two
instances of the same fanned-out slot answers the one they picked — not whichever an alias lookup would have found.

**The compiler checks less here, and nothing enforces less.** The event and its arguments are supplied at the call
site rather than derived from a slot the author named, so a mistake that the static form would catch at compile time
is caught at run time instead. What is *allowed* is unchanged: the same authorization, the same requirements, the same
quorum.

## See also       {#see-also}
- [Workflow.Inbox&lt;T&gt; (what is waiting for me)](https://osysharp.com/reference/workflow/inbox/) — the queue these verbs act on
- [Candidates (slot)](https://osysharp.com/reference/workflow/candidates/) — who may hold or satisfy a slot, and `<Wf>.For(item).<Slot>.Candidates(u)` for a screen
  deciding whether to OFFER the claim in the first place
- [Assign — handing a slot to a named colleague](https://osysharp.com/reference/workflow/assign/) — handing a slot to a named colleague, and the `Reassign` rule that permits it
- [Requires — named preconditions, and the live checklist](https://osysharp.com/reference/workflow/requires/) — the criteria a deposit must satisfy, and how to show them before the click
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — declaring the slot and the event it waits for
