# What a workflow body may write

> A workflow body runs on a system data context, so its writes are NOT gated by the entity's `security {}` block. The person who raised the event is authorized to RAISE it — `[Authorize]` and a slot's `Candidates` decide that — and everything the transition then writes lands regardless of what that person could have written themselves.

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

## Summary        {#summary}
A workflow body writes as the **system**, not as the caller. The entity's `security {}` block gates what a PERSON
may do directly; it does not gate what a transition does once an event has been legitimately raised.

## Signature      {#signature}
```osy title="the write inside a transition is not checked against the entity's rules" syntax
state Open {
  on Close {
    this.Item.Note = "closed by the workflow";   // lands even if NO rule grants `update` to anyone
    goto Closed;
  }
}
```

## Description    {#description}

### Where the authorization actually happens   {#where}
Two different questions, and only the first one is about the person:

- **May they raise this event?** `[Authorize(principal => …)]` on the event, and a slot's `Candidates` for who may
  hold the work. See [[Authorize] (event)](https://osysharp.com/reference/workflow/authorize/).
- **May the body write this row?** Not asked. The engine opens a system data context for the run, and a system
  context is defined as one that opts out of data security.

So a transition is trusted code, in the same sense a server function's own bookkeeping is. Put the gate on the
EVENT, where the person is.

### Why it is built that way   {#why}
A run outlives the request that started it. A milestone that expires at 2am, a deadline, a retry after a service
came back — these resume with no acting principal at all, and a rule written `allow update where Owner == user`
has no `user` to compare against. If a body were gated by the caller's rules, the same transition would succeed or
fail depending on who happened to trigger it, and a timer-driven one could never succeed.

### What this means for your model   {#consequences}
- **Do not rely on `security {}` to stop a workflow.** If a field must never change once a run owns it, that is a
  guard in the body or an `[Immutable]` on the member — not a missing `allow update`.
- **A read from a page is still gated.** This is about the BODY. What the person then sees on screen goes through
  the ordinary read rules, unchanged.
- **`[Authorize]` is the real perimeter.** An event anybody may raise is an event anybody may cause every write in
  its transition to happen.

## Examples       {#examples}
A whole app, compiled: `Ticket` grants read and create and **no update to anyone**, and the transition writes
`Note` anyway.

```osy title="the body writes a field no rule grants" test app=workflow-body-security
enum TicketState { Open, Closed }

entity Ticket {
  [MaxLength(80)] string Title;
  TicketState Status = TicketState.Open;
  [MaxLength(200)] string Note = "";
  security { allow read, create when IsAnonymous || IsAuthenticated; }   // no `update`, to anyone
}

workflow TicketFlow {
  Tracks  = Ticket.Status;
  Initial = Open;

  event Close();

  state Open {
    on Close {
      this.Item.Note = "closed by the workflow";   // lands: a body is not gated by the block above
      goto Closed;
    }
  }
  state Closed { }
}

void CloseIt(Ticket t) { TicketFlow.RaiseClose(t); }
```

Gate the EVENT, which is where the person is:

```osy title="gate the EVENT, not the row" syntax
[Authorize(principal => principal == this.Item.Owner)]
event Close();
```

## See also       {#see-also}
- [[Authorize] (event)](https://osysharp.com/reference/workflow/authorize/) — who may raise an event
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — who may hold a work slot
- [security { }](https://osysharp.com/reference/security/entity-security/) — what the `security {}` block gates
