# Candidates (slot)

> Declares WHO may hold or satisfy a `subscribe` slot. `Candidates` is one expression surface that dispatches on its return type: a `principal => bool` PREDICATE selects the eligible pool by a rule; a `() => List<Principal>` COMPUTATION returns the eligible set outright, computed off the run's data (`this.Item` is in scope). A principal not eligible is refused when they try to claim or deposit. Returning anything else is a compile error.

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

## Summary        {#summary}
`Candidates` on a `subscribe` slot declares WHO may **hold or satisfy** that slot. It is a single expression surface
that the compiler **dispatches on by return type**:

- returns **`bool`** → a **predicate** (`u => u.Team == Team.Support`): the eligible pool is every principal for whom the
  rule holds.
- returns **`List<Principal>`** → a **computation** (`() => Agent.Where(u => u.Region == this.Item.Region &&
  u.OnCall).ToList()`): the eligible set is exactly the list you return, computed fresh off the run's data.

Either way a principal who is **not eligible** is refused when they try to claim or deposit. Returning any other type
(a scalar, a single principal) is a **compile error**. This is who-may-**HOLD** authorization — a different question from
`[Authorize]`, which is who-may-**RAISE** an event.

## Signature      {#signature}
```osy syntax
subscribe <Event>() as <Alias> {
  Candidates = <principal> => <predicate>;              // a bool predicate  → the pool by a rule
  // — or —
  Candidates = () => <expression returning List<Principal>>;   // a computation → the eligible set
}
```

## Description    {#description}
A slot with `Candidates` is a **pool slot**: work that any eligible principal may pick up. The gate is a **membership
check** in both forms — "is this principal one of the eligible set?" — evaluated fail-closed every time a principal
tries to claim the slot or deposit into it.

### The predicate form — a rule   {#predicate}
The predicate form is a single-parameter lambda whose parameter is the principal being tested, typed as the app's
`[Principal]` entity. `this.Item` (the tracked entity) is in scope, so the rule can compare the principal to the item —
a role test, an ownership test, a four-eyes exclusion:

```osy title="a bool predicate — the pool by a rule" syntax app=support-triage
subscribe Decide() as Legal {
  Candidates = u => u.Department == Dept.Legal && u != this.Item.Requester;   // four-eyes
}
```

### Reading a sibling slot — cross-slot four-eyes   {#sibling-slot}
A rule often has to exclude **whoever already acted**, not whoever raised the item. Name the sibling slot by its `as`
alias and read who holds it:

```osy title="the second approver may not be the first" test app=workflow-candidates-four-eyes
enum PoStage { Review, Done }

[Principal] entity Person {
  [Required, MaxLength(80)] string Name;
  security { allow read, create when IsAuthenticated; }
}

entity Po {
  [Required, MaxLength(120)] string Title;
  PoStage Stage;
  security { allow read, create, update when IsAuthenticated; }
}

workflow PoApproval {
  Tracks    = Po.Stage;
  Autostart = true;
  Initial   = Review;

  event Approve();

  state Review {
    subscribe Approve() as First { }

    subscribe Approve() as Second {
      After      = [First];                    // First is satisfied before this opens…
      Candidates = u => u != First.Assignee;   // …so its assignee is who really approved
    }

    on Complete { goto Done; }
  }

  terminal success Done { }
}
```

Three members read off a sibling: **`.Assignee`** (the principal holding it, or nothing while it is unheld),
**`.Status`**, and **`.IsUnassigned`**. They answer for **this run**, so each item in flight is judged against its own
history rather than against a rule written once for all of them.

⚠ **Order is part of the rule, so make it explicit.** The gate is evaluated when someone tries to claim or deposit, and
a slot nobody holds yet has no assignee to exclude — so `u != First.Assignee` admits everyone until First is taken.
[`After`](https://osysharp.com/reference/workflow/slot-dependencies/) is what makes the exclusion mean what it reads like: with it, the second slot
does not exist to anybody until the first is satisfied.

⚠ **A FANNED-OUT sibling is refused**, because that alias names one slot per element and the read cannot say which.
Answering with an arbitrary instance would admit exactly the people the other instances exclude — a wrong *allow*,
which is the one direction an authorization rule must never fail in. Compare against a slot that names exactly one, or
decide it in the route arm once the fan-out is satisfied.

### The computation form — the set   {#computation}
The computation form returns the eligible set as a `List<Principal>` — arbitrary Osy# that queries or assembles the
list. `this.Item` is **ambiently in scope**, so the set is computed against the run's own data. The primary shape is a
zero-parameter lambda; a named function that returns a list works too. It is computed **fresh, on demand** each time
the gate runs — never stored or materialised:

```osy title="a List<Principal> computation — the eligible set, computed off the run" syntax app=support-triage
subscribe Handle() as Owner {
  Candidates = () => Agent.Where(u => u.Region == this.Item.Region && u.OnCall).ToList();
}
```

Use the computation form when eligibility is a **query over data** rather than a rule over one principal — "the on-call
agents in this ticket's region", "everyone on the account team for this order" — especially when the set depends on
relationships the item points at.

### Return-type dispatch and errors   {#dispatch}
The compiler decides the form from the resolved return type: `bool` → predicate, `List<Principal>` → computation. A
`Candidates` that returns anything else is rejected at compile time:

```text
`Candidates` must return either `bool` (a `principal => predicate`) or `List<Principal>`
(a computation returning the eligible set) — got 'Agent'.
```

### Fail-closed   {#fail-closed}
In both forms the gate refuses when it cannot positively establish membership: no acting principal, no `[Principal]`
entity, an unresolvable principal, or an empty computed set → **not a candidate**. A refused claim or deposit throws and
is recorded on the workflow's audit timeline; the entity does not move.

### Acting on a pool slot does NOT claim it   {#acting-does-not-claim}
A pool slot — one with `Candidates` and no `Assignee` — is held by **nobody** until someone calls `Claim()`. Depositing
its event satisfies the slot **without ever assigning it**, so `Assignee` is still nothing inside the route arm. That
is correct and deliberate: `Assignee` records *whose queue this sat in*, and on a pool slot nobody ever queued it.

**To credit the decision, name the actor — not the assignee.** [`actor`](https://osysharp.com/reference/workflow/actor/) is the principal whose
action drove the body, and it has an answer whether the slot was claimed, unclaimed, or acted on by somebody it was
never assigned to:

```osy title="credit who decided, not whose queue it was in" syntax
on Legal(Decision decision, string reason) {
  this.Item.ReviewedBy = actor;   // WHO DECIDED — always answers
}
```

⚠ **`slot.Assignee` is the trap here**, and it fails quietly: on an unclaimed pool slot it records nothing at all,
and the null surfaces hops later wherever it is used —

> `Employee.Single(e => e.User == decidedBy) matched no rows (Employee has 4 rows) — `decidedBy` was null.`

⚠ **And `Session.CurrentUser` is not the answer either** — it is refused inside a workflow body, because a body runs
on the engine's own authority and may resume after a park with nobody signed in (see [What a workflow body may write](https://osysharp.com/reference/workflow/body-security/)).
The compiler names `actor` when you write it.

**Claiming first is a real thing to do, but it is not how you get an actor.** Claim when you want the slot to *stop*
being a pool — to take it out of everyone else's queue while you work on it:

```osy title="claiming takes it out of the pool — a different intent from recording who acted" syntax
if (PoApproval.For(po).Legal.IsUnassigned) { PoApproval.For(po).Legal.Claim(); }
```

Claiming *in order to record somebody* would write down a falsehood: it registers whoever acted as the **assigned
approver**, which on a pool slot they never were, and on a slot assigned to someone else it is simply the wrong name.

A slot with an `Assignee` setting is handed to that person when it arms — so there `Assignee` answers, and `actor` is
still the one that says who actually acted. The two are different questions; see [actor — who just did this](https://osysharp.com/reference/workflow/actor/).

### May this person claim? Asking the rule yourself   {#asking}
A screen that offers a **Claim** button has to know whether the viewer may claim — and the only honest answer is the
slot's own rule. Ask it:

```osy title="asking a named run whether this person may claim" test app=workflow-candidates-ask
enum PoStage { Draft, Review, Done }
enum Dept { Legal, Finance }

[Principal] entity Person {
  [Required, MaxLength(80)] string Name;
  Dept Department = Dept.Legal;
  security { allow read, create when IsAuthenticated; }
}

entity Po {
  [Required, MaxLength(120)] string Title;
  PoStage Stage;
  [Required] Person Requester;
  security { allow read, create, update when IsAuthenticated; }
}

workflow PoApproval {
  Tracks    = Po.Stage;
  Autostart = true;
  Initial   = Draft;

  event Submit();
  event Approve();

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

  state Review {
    subscribe Approve() as Legal {
      // Four-eyes: a Legal approver, and never the person who raised it.
      Candidates = u => u.Department == Dept.Legal && u != this.Item.Requester;
    }
    on Legal { goto Done; }
  }

  terminal success Done { }
}

// What a screen asks before it draws a Claim button — the slot's OWN rule, not a second copy of it.
bool MayClaimLegal(Po po, Person who) {
  return PoApproval.For(po).Legal.Candidates(who);
}
```

It **inlines the declared predicate**, so there is one expression of the rule rather than two. That matters more than
it sounds: a page that re-types the rule drifts from the slot silently, and always in the worse direction — offering a
button the deposit then refuses, or hiding one that would have been accepted. Because it inlines rather than calling
the engine, it also lowers into the surrounding read, so it is legal in a `live var` and in a client-rendered page.

Inside a [milestone](https://osysharp.com/reference/workflow/milestone/) body the same question is `slot.Candidates(u)`, where the run is ambient
rather than named. Both forms answer identically, and both accept either declaration form — against a computation the
call becomes a membership test over the computed set.

**On a FANNED-OUT slot, naming the instance is what binds the fan-out variable.** A predicate like
`Candidates = u => u.Hat == h` reads the loop variable, so `.Architect.Candidates(u)` and `.Security.Candidates(u)`
ask two different questions from one declaration — the variable substitutes to that instance's own element.

⚠ **A DYNAMIC fan-out is refused**, because its slots have no static names — they are addressed at run time by the
acting principal, so there is no single slot for the question to be about. Use [`Workflow.Inbox<T>()`](https://osysharp.com/reference/workflow/inbox/)
there, which answers it for the caller.

⚠ **A slot that declares no `Candidates` is REFUSED here, not answered `true`.** Such a slot admits everyone, so the
question has no content, and a caller guarding on a constant is guarding on nothing:

```console
slot 'Legal' declares no `Candidates`, so every principal is eligible and `.Candidates(...)` has nothing to answer.
Drop the check, or declare `Candidates` on the slot.
```

⚑ **The check is a courtesy, never the gate.** Authorization happens at the deposit, server-side, whatever the screen
drew — so a page that offers the wrong button is a cosmetic bug rather than a security one. That is the right
direction, and it is why this may be used freely in UI.

## Examples       {#examples}
Eligibility as a rule — only a Legal approver who is not the requester (four-eyes):

```osy title="predicate: rule over the tested principal" syntax app=support-triage
subscribe Decide() as Legal {
  Candidates = u => u.Department == Dept.Legal && u != this.Item.Requester;
}
```

Eligibility as a computed set — the on-call agents in the ticket's region:

```osy title="computation: the eligible set off the run's data" syntax app=support-triage
subscribe Handle() as Owner {
  Candidates = () => Agent.Where(u => u.Region == this.Item.Region && u.OnCall).ToList();
}
```

## See also       {#see-also}
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — the `subscribe` slot `Candidates` lives on
- [[Authorize] (event)](https://osysharp.com/reference/workflow/authorize/) — `[Authorize]` on an event: who may RAISE (contrast with who may HOLD here)
- [Assign — handing a slot to a named colleague](https://osysharp.com/reference/workflow/assign/) — `Reassign`: who may MOVE a slot. A third question again, and eligibility to hold work is
  deliberately not authority over it — a principal `Candidates` admits still cannot take a slot off its holder
