Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Workflow

[Authorize] (event)

[Authorize(principal => <predicate>)] event Name(params);

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`).

stable1 example compiled by CIworkflowauthoringsecurity

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#

[Authorize(<principal> => <predicate over the principal and this.Item>)]
event <Name>(<typed params>);

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#

[Authorize] and a slot's Candidates answer different questions and do not substitute for each other:

QuestionScope
[Authorize] on an eventwho may raise this eventthe event (workflow-wide)
Candidates on a subscribewho may hold / satisfy this slotone 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#

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#

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:

[Authorize(u => u == this.Item.Requester || u.Role == Role.Support)]
event Cancel();

on Cancel { goto Cancelled; }

The requester alone may cancel:

[Authorize(u => u == this.Item.Requester)]
event Cancel();

See also#

  • subscribe — a slot's Candidates, the who-may-HOLD gate (contrast with who-may-RAISE here)

Related

subscribe

Declares that a workflow state waits on an event, and configures the wait — who may hold it, who may hand it on…