# Raising a workflow event

> Raise a typed event on the run bound to an entity, from anywhere — an ordinary server function, a webhook handler, a signup step. Two spellings do it: the NAMED form states which workflow, and the INFERRED form works it out from the entity's type, which is what generic code wants when it should not have to know.

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

## Summary        {#summary}
An event is how the outside world moves a workflow along. Raising one advances the run bound to an entity — routing
to the slot in the current state that subscribes it, or to a workflow-level arm that handles it wherever the run has
got to.

There are two spellings, and the only difference is whether the call **names** the workflow:

```osy syntax
OrderFlow.RaisePayment(order, 100);       // NAMED    — you say which workflow
Workflow.Raise(order, Payment(100));      // INFERRED — worked out from the entity's type
```

Both drive the same engine, enforce the same [[workflow-authorize|`[Authorize]`]] gate, and route the same way.
Neither is a test-only surface: an ordinary server function may raise an event, and that is the normal way an app
advances a run from outside the workflow.

## Signature      {#signature}
```osy syntax
<Workflow>.Raise<Event>(entity, args…)    // named: the method is the workflow's, one per declared event
Workflow.Raise(entity, <Event>(args…))    // inferred: the event is written as a constructor call
```

`entity` is the row the run is bound to — whatever the workflow [`Tracks`](https://osysharp.com/reference/workflow/tracks/). The arguments are the
event's declared parameters, in order, and they arrive in the arm **by name**.

## Description    {#description}

### Which spelling to use   {#spellings}
Reach for the **named** form by default. It is the one that reads back as what it does — the workflow is on the page,
so anyone changing the model can find every producer of an event by searching for it, and nothing about the call
depends on facts elsewhere in the model.

Reach for the **inferred** form when the caller genuinely should not know the workflow: shared helpers, generic
plumbing, anything written against "an entity that has a workflow" rather than against one particular flow. It is
narrower than it looks — most application code knows perfectly well which workflow it is advancing, and writing that
down costs one identifier.

### ⚠ The inference needs the entity's type to have exactly ONE workflow   {#inference}
The inferred form resolves the workflow from the entity's **type**. If two workflows bind that type, there is nothing
to infer, and the compiler refuses the call rather than picking one:

```text
Workflow.Raise: more than one workflow in this unit binds 'Ticket', so the workflow cannot be inferred from the
entity — name it instead: `<Workflow>.RaiseResolve(t)`
```

This is a **compile** error on purpose, and the reason is the same as the reason the form exists. Generic code is
precisely the caller that cannot check: a helper holding somebody else's entity has no way to notice that the type
has since acquired a second workflow. So the check belongs where the whole model is in view. Adding a second workflow
to a type is a change that will name every inferred call that has just become ambiguous, and each is a one-word fix.

### Where it routes   {#routing}
Raising does not name a slot. The engine takes the event to:

- the slot in the run's **current state** that [subscribes](https://osysharp.com/reference/workflow/subscribe/) it — the ordinary case; or
- a **workflow-level** arm for it, wherever the run has got to — how a `Cancel` reaches a run in any state.

An event no slot is waiting for and no workflow-level arm handles is **refused**, not queued. To answer a specific
queued slot from a person's inbox — where the row, not the code, decides which slot is being answered — use
[`Workflow.Deposit(row, …)`](https://osysharp.com/reference/workflow/inbox-act/) instead.

### The run has to exist   {#run-required}
Both forms raise on the run **already bound** to the entity: they do not start one. If nothing has started a run for
that row, the call fails saying so — [`Workflow.Run(entity)`](https://osysharp.com/reference/workflow/run/) (or `Autostart`) is what creates it.
When a row has more than one run over its lifetime, a run still **waiting** is preferred over a finished one.

### Authorization is the workflow's, not the caller's   {#authorization}
An event's `[Authorize]` predicate and a slot's `Candidates` are enforced by the engine, so they hold identically for
both spellings and for a raise from ordinary application code. Being able to call the function is not permission to
advance the run.

## Examples       {#examples}

An ordinary server function advancing a run — the signup / webhook shape, with the workflow named:

```osy title="named" test app=workflow-raise-named
enum TicketStatus { Working, Closed }

entity Ticket {
  [Required, MaxLength(200)] string Title;
  TicketStatus Status = TicketStatus.Working;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

workflow TicketFlow {
  Tracks  = Ticket.Status;
  Initial = Working;
  event Resolve();
  state Working {
    subscribe Resolve();
    on Resolve { goto Closed; }
  }
  terminal success Closed { }
}

void ResolveTicket(Ticket t) {
  TicketFlow.RaiseResolve(t);
}
```

The same call without naming the workflow, and with an argument the arm routes on:

```osy title="inferred" test app=workflow-raise-inferred
enum ExpenseStatus { Filed, Approved, Rejected }

entity Expense {
  [Required, MaxLength(200)] string Memo;
  ExpenseStatus Status = ExpenseStatus.Filed;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

workflow ExpenseApproval {
  Tracks  = Expense.Status;
  Initial = Filed;
  event Decide(bool approved);
  state Filed {
    subscribe Decide(bool approved);
    on Decide(bool approved) {
      when (approved) { goto Approved; }
      default { goto Rejected; }
    }
  }
  terminal success Approved { }
  terminal error   Rejected { Message = "rejected"; }
}

void DecideExpense(Expense e) {
  Workflow.Raise(e, Decide(true));
}
```

## See also       {#see-also}
- [Workflow.Run (start a workflow)](https://osysharp.com/reference/workflow/run/) — starting the run this raises on, and the awaited child-workflow form
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — the slot that waits for the event, and who may satisfy it
- [Acting on an inbox row (deposit, claim, release)](https://osysharp.com/reference/workflow/inbox-act/) — answering a specific queued slot from an inbox row instead
- [[Authorize] (event)](https://osysharp.com/reference/workflow/authorize/) — the produce-side gate on who may raise an event
