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

Reference / Workflow

Raising a workflow event

<Workflow>.Raise<Event>(entity, args…) · Workflow.Raise(entity, Event(args…))

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.

stable2 examples compiled by CIworkflowauthoring

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:

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#

<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. The arguments are the event's declared parameters, in order, and they arrive in the arm by name.

Description#

Which spelling to use#

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#

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:

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#

Raising does not name a slot. The engine takes the event to:

  • the slot in the run's current state that subscribes 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, …) instead.

The run has to exist#

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) (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#

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#

An ordinary server function advancing a run — the signup / webhook shape, with the workflow 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:

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#

Related

Workflow.Run (start a workflow)

Start the workflow bound to an entity's type, on that entity. Bare — `Workflow.Run(order)` — is fire-and-forget: start…

subscribe

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

Acting on an inbox row (deposit, claim, release)

Answer a queued slot from the row itself. The event is named at the call site because a queue's rows are heterogeneous…

[Authorize] (event)

Gates WHO may raise a workflow event. `[Authorize]` on an event is a `principal => bool` predicate over the acting…