# [Authorize] (event)

> Gates WHO may raise a workflow event. `[Authorize]` on an event is a `principal => bool` predicate over the acting principal and `this.Item`; a principal who fails it is refused when they try to raise the event. This is produce-side authorization — a distinct question from a slot's `Candidates` (who may HOLD a work item), and the only way to gate an event that has no slot (e.g. a `Cancel`).

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

## Summary        {#summary}
`[Authorize]` on a workflow `event` declares WHO may **raise** it. It is a single-parameter `principal => <predicate>`
lambda — the parameter is the acting principal, and `this.Item` (the workflow's tracked entity) is in scope — evaluated
when the event is raised. A principal who does not satisfy the predicate is refused; the raise never runs a route or a
transition. This is **produce-side** authorization: a different question from a slot's `Candidates` (who may HOLD a work
item), and the answer for an event that has no slot to hang authorization on — the canonical case being a `Cancel` that
any state should honour but only certain principals may trigger.

## Signature      {#signature}
```osy syntax
[Authorize(<principal> => <predicate over the principal and this.Item>)]
event <Name>(<typed params>);
```

## Description    {#description}
An event is a thing the outside world can raise on a running workflow. Left undecorated, anyone with access to the
workflow's surface may raise it. `[Authorize]` narrows that: it is a boolean predicate the engine evaluates against the
**acting principal** the moment the event is raised, before any routing or transition happens.

- The lambda takes exactly **one parameter** — the acting principal — typed as the app's `[Principal]` entity.
- `this.Item` (the tracked entity) is in scope, so the predicate can compare the principal to the item — the common
  shape is ownership (`u => u == this.Item.Requester`) or a role/relationship test
  (`u => u == this.Item.Requester || u.Role == Role.Support`).
- The predicate is **fail-closed**: if there is no acting principal, no `[Principal]` entity, or the principal cannot be
  resolved, the raise is refused.

A refused raise **throws** — it does not route to an `on <Event>.Denied` arm (that would invite an author to write an
empty one and silently swallow a security failure). The refusal is recorded on the workflow's audit timeline, so "who
tried to raise this and was refused" is a query, and the entity does not move.

### Relationship to `Candidates`   {#vs-candidates}
`[Authorize]` and a slot's `Candidates` answer different questions and do not substitute for each other:

| | Question | Scope |
|---|---|---|
| `[Authorize]` on an `event` | who may **raise** this event | the event (workflow-wide) |
| `Candidates` on a `subscribe` | who may **hold / satisfy** this slot | one slot in one state |

An event with no slot (`Cancel`) can only be gated with `[Authorize]`. A slot in a specific state that different
principals may act on is gated with `Candidates`. A workflow may use both.

### Raising a gated event from anywhere   {#workflow-route}
A workflow-level route (`on Cancel { goto Cancelled; }`, declared once beside the states) is live in every non-terminal
state, so a gated `Cancel` can be raised at any point in the run and the same route decides where it goes. A state may
override the workflow-level route for that event by declaring its own `on <Event>` (nearest-wins).

## Examples       {#examples}
```osy title="who may raise the event" test app=workflow-authorize
enum Decision   { Approve, Reject }
enum OrderState { Placed, Done }

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

entity Order {
  [Required, MaxLength(60)] string Reference;
  [Required] Person Requester;
  OrderState Status;                       // no default: the workflow owns this field
  security { allow read, create, update when IsAuthenticated; }
}

workflow OrderFlow {
  Tracks    = Order.Status;
  Autostart = true;
  Initial   = Placed;

  // The rule travels with the EVENT, so every path that could raise it is covered by one line.
  [Authorize(u => u == this.Item.Requester)]
  event Cancel();

  state Placed {
    subscribe Cancel();
    on Cancel { goto Done; }
  }
  terminal success Done { }
}
```

Only the requester or a support agent may cancel an order, and a cancellation is honoured from any state:

```osy title="an authorized event with a workflow-level route" syntax app=purchase-approval
[Authorize(u => u == this.Item.Requester || u.Role == Role.Support)]
event Cancel();

on Cancel { goto Cancelled; }
```

The requester alone may cancel:

```osy title="ownership gate" syntax app=purchase-approval
[Authorize(u => u == this.Item.Requester)]
event Cancel();
```

## See also       {#see-also}
- [What a workflow body may write](https://osysharp.com/reference/workflow/body-security/) — what authority the workflow's OWN body writes with (an `enter`, an `on` handler): the
  engine's, not the raiser's — the question every invitation flow asks next, and the one this page does not answer
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — a slot's `Candidates`, the who-may-HOLD gate (contrast with who-may-RAISE here)
