# slot dependencies (After / When / Pending)

> Per-slot ordering and conditioning. `After = [A, B]` holds a slot CLOSED (status `Pending`, no clock) until every named sibling slot is satisfied; `When = <predicate>` decides — WHEN THE SLOT WOULD OPEN — whether it exists at all. Together they express the parallel-then-serial shape a real approval chain has.

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

## Summary        {#summary}
Two per-slot settings order and condition a wait. `After = [A, B]` makes a slot open only once every named sibling
slot is satisfied — until then it sits **`Pending`**: visible, but not claimable and with no SLA clock running.
`When = <predicate>` decides whether the slot EXISTS at all, evaluated **when the slot would open** (not at state
entry) — so a value that changes mid-review (an expense amount raised past a threshold) correctly grows a slot that
did not exist before. Slots with neither open immediately, in parallel.

## Signature      {#signature}
```osy syntax
subscribe <Event>(<typed params>) as <Alias> {
  When  = <predicate over this.Item>;      // the slot only EXISTS while this holds
  After = [<SiblingAlias>, <SiblingAlias>]; // …and only OPENS once all of these are satisfied
}
```

## Description    {#description}
- **`After` — a dependency, not a mode.** `After` names sibling slots of the same state. A slot with predecessors is
  created **`Pending`** at state entry and cannot be claimed or deposited into (a deposit is refused, like an
  out-of-pool one); no clock runs, so it can never breach for a predecessor's slowness. When the LAST predecessor is
  satisfied the slot **opens** — `Unassigned` (or `Assigned` if it declares an `Assignee`) — and its SLA clock starts.
- **`When` is evaluated at OPEN time.** For a slot with `When` and `After`, the condition is checked at the moment its
  predecessors complete, over the current `this.Item`. A slot whose `When` is false is **never created** — not
  `Pending`, not shown, not counted toward completion. If the condition only becomes true later (the amount was
  raised), the slot **grows** then. For a slot with `When` but no `After`, the condition is evaluated at state entry.
- **Completion.** `on Complete` fires when every slot that EXISTS AND IS OPEN is satisfied. A `When`-false (never
  created) slot does not block completion; a `Pending` or open-but-unsatisfied slot does.
- **Observing it — `SlotStatus` + `.Slots`.** The `SlotStatus` enum (`Pending`, `Unassigned`, `Assigned`,
  `Satisfied`, `Cancelled`, `Breached`) is the type of `Wf.For(entity).<Slot>.Status`. `Wf.For(entity).Slots` returns
  the run's live slots (`List<SlotView>`, each with `.Name` and `.Status`) — a slot the `When` gate never created is
  simply absent, so `Wf.For(e).Slots.Any(s => s.Name == "Cfo")` is false for an expense below the threshold.

## Examples       {#examples}
Manager and Finance approve in parallel; the CFO slot opens only after both, and only for large expenses:

```osy title="a slot that opens only after both, and only if large" test app=workflow-slot-dependencies
enum Decision      { Approve, Reject }
enum RequisitionStatus { Approvals, Approved, Rejected }
enum Role          { Staff, Finance, Cfo }
enum Dept          { Engineering, Finance, Legal }

// The entities the workflow reaches into. The example named all four and declared none — which the
// gate could not see while the fence was exempt from it.
[Principal]
entity Person {
  [Required, MaxLength(200)] string Email;
  Role Role = Role.Staff;
  Dept Department = Dept.Engineering;
  Person Manager;
  security { allow read, create when IsAuthenticated; }
}

entity Requisition {
  [Required] Person Employee;
  decimal Cost;
  RequisitionStatus Status;   // no default: the workflow owns this field
  security { allow read, create, update when IsAuthenticated; }
}

entity Approval {
  [Required] Requisition Requisition;
  Person By;
  DateTime At;
  security { allow read, create when IsAuthenticated; }
}

workflow RequisitionApproval {
  Tracks    = Requisition.Status;
  Autostart = true;
  Initial   = Approvals;

  event Approve(Decision decision);

  state Approvals {
    subscribe Approve(Decision decision) as Manager {
      Assignee = this.Item.Employee.Manager;
    }
    subscribe Approve(Decision decision) as Finance {
      Candidates = u => u.Department == Dept.Finance;
    }
    // Pending until BOTH predecessors are satisfied — and only exists for large expenses.
    subscribe Approve(Decision decision) as Cfo {
      When       = this.Item.Cost > 10000;
      After      = [Manager, Finance];
      Candidates = u => u.Role == Role.Cfo;
    }

    on Approve(Decision decision, Slot slot) {
      new Approval { Requisition = this.Item, By = slot.Assignee, At = DurableClock.Now };
    }
    on Complete { goto Approved; }
  }

  terminal success Approved { }
  terminal error   Rejected { Message = "expense rejected"; }
}
```

Observing the CFO slot's lifecycle from a test (or a UI):

```osy title="watching Pending open the moment predecessors land" syntax
Assert.Equal(SlotStatus.Pending, RequisitionApproval.For(e).Cfo.Status);      // visible, not open
runas (Mia)  { RequisitionApproval.For(e).Manager.Approve(Decision.Approve); }
Assert.Equal(SlotStatus.Pending, RequisitionApproval.For(e).Cfo.Status);      // one predecessor down
runas (Otto) { RequisitionApproval.For(e).Finance.Approve(Decision.Approve); }
Assert.Equal(SlotStatus.Unassigned, RequisitionApproval.For(e).Cfo.Status);   // NOW it opens — clock starts

// a small expense never grows a CFO slot at all
Assert.False(RequisitionApproval.For(small).Slots.Any(s => s.Name == "Cfo"));
```

## See also       {#see-also}
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — the wait these settings condition
- [Requires — named preconditions, and the live checklist](https://osysharp.com/reference/workflow/requires/) — the other completion gate (a quorum predicate)
- <span class="planned" title="this page is planned and not written yet">workflow-route</span> — the event-keyed arm shared by the sibling slots (and its `Slot slot` param)
- [fan-out (foreach subscribe)](https://osysharp.com/reference/workflow/fan-out/) — many parallel slots from one declaration
- <span class="planned" title="this page is planned and not written yet">workflow-state</span> — the enclosing state
