# For(entity).Audit

> Reads a running instance's lifecycle timeline — every transition, claim, deposit, reminder and refusal as an WorkflowAuditEntry you can filter and count. The artifact ops reads in a dispute, and the one a reminder is observed through.

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

## Summary        {#summary}
`<Workflow>.For(entity).Audit` returns the instance's lifecycle **timeline** as a `List<WorkflowAuditEntry>` — one row per
thing that happened to the run, oldest first. It is the record ops reads to explain an outcome, and the only way to
observe events that change no state, such as a fired reminder.

## Signature      {#signature}
```osy syntax
<Workflow>.For(<entity>).Audit   // → List<WorkflowAuditEntry>
```
Each `WorkflowAuditEntry` carries:

- **`Kind`** — an [[workflow-audit#kinds|AuditKind]] (`Transitioned`, `Claimed`, `Released`, `Reminded`, `Refused`, `Deposited`, …);
- **`Slot`** — the slot alias the event concerns (`"Legal"`), or nothing for a non-slot event;
- **`Actor`** — the principal that acted, or nothing for an engine-fired event (a reminder, a deadline);
- **`Message`** — a short reason carried by the event;
- **`At`** — when it occurred;
- **`From`** / **`To`** — the state the run was in and the state it moved to, **by name**, as recorded when the event
  was written — so a `Transitioned` entry says WHERE it went (`"Paid"`, `"Refunded"`), and still says so after the run
  migrates to a newer version. For an event that moved nothing (a claim, a reminder) both name where the run was.

## Description    {#description}
The timeline is computed on read from the instance's recorded events; app code never writes it. It comes in the
order things **happened**: by `At`, and events of one instant — a run that goes through several states at once, each
entry going straight on — in the order they happened within it. Assign it to a local and query it with ordinary list
operations:

```osy title="the timeline of one run" test app=workflow-audit
using Osysharp.Workflow;

enum ClaimStatus { Filed, Settled }

entity Claim {
  [Required, MaxLength(120)] string Reference;
  ClaimStatus Status;                 // no default: an autostarting workflow supplies the starting value
  // IsAnonymous is here because a [Test] body runs as an anonymous caller: without it the seed in the test below
  // is refused, and the example would document a create that cannot happen.
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

workflow ClaimFlow {
  Tracks    = Claim.Status;
  Autostart = true;                   // …so a Claim has a run — and therefore a timeline — from the moment it exists
  Initial   = Filed;
  event Approve();
  state Filed {
    subscribe Approve();
    on Approve { goto Settled; }
  }
  terminal success Settled { }
}

// The timeline is computed on read from the run's recorded events — app code never writes it.
int RefusalsFor(Claim c) {
  var audit = ClaimFlow.For(c).Audit;
  return audit.Where(a => a.Kind == AuditKind.Refused).Count();
}

// The move that settled the claim — the transition whose `To` names the state; its `At` is when. Null until then.
WorkflowAuditEntry? Settling(Claim c) {
  return ClaimFlow.For(c).Audit.FirstOrDefault(a => a.Kind == AuditKind.Transitioned && a.To == "Settled");
}
```

A **reminder** has no state change to see, so a test that a reminder fired asserts on the audit — the same artifact a
human would inspect — rather than on a bespoke hook. Tests and audits agree by construction: the assertion cannot pass
unless the thing ops would see actually happened.

### What kinds of event are recorded?        {#kinds}
`AuditKind` names what happened: `Transitioned` · `Claimed` · `Released` · `Assigned` · `Cancelled` · `Reminded` ·
`Breached` · `Held` · `Resumed` · `Retargeted` · `Refused` · `Deposited`.

### A row with no run        {#no-run}
A row may have no run to read: its finished run was reaped by [`app.Workflow.Retention`](https://osysharp.com/reference/config/workflow/), it was
written before its workflow existed, or its workflow does not start on its own and nothing has started one.
`For(row)` then raises a **`NotFoundException`** — so catch that, and only that, where an empty history is an ordinary
answer. A `catch (Exception)` here would also swallow a real failure.

```osy title="a row with no run" run app=workflow-audit-no-run
using Osysharp.Workflow;

enum VisitStage { Booked, Done }

entity Visit {
  [Required, MaxLength(120)] string Who;
  VisitStage Stage = VisitStage.Booked;   // the workflow does not start on its own, so the field has its own default
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

workflow VisitFlow {
  Tracks    = Visit.Stage;
  Autostart = false;                  // a Visit has a run only once something starts one
  Initial   = Booked;
  event Finish();
  state Booked {
    subscribe Finish();
    on Finish { goto Done; }
  }
  terminal success Done { }
}

List<WorkflowAuditEntry> HistoryOf(Visit v) {
  try { return VisitFlow.For(v).Audit; }
  catch (NotFoundException) { return []; }   // no run: nothing to tell, and nothing went wrong
}

[Test]
void a_row_with_no_run_has_an_empty_history() {
  var v = new Visit { Who = "Ines" };
  UnitOfWork.Commit();
  Assert.Equal(0, HistoryOf(v).Count);   // the catch took it: any other fault would fail this test
}
```

## Examples       {#examples}
```osy title="asserting that a reminder fired" run app=workflow-audit
using Osysharp.Workflow;

[Test]
void a_reminder_shows_up_on_the_timeline() {
  var c = new Claim { Reference = "C-1" };
  UnitOfWork.Commit();

  TestClock.Advance(TimeSpan.FromHours(2));

  // The assertion reads the same artifact ops would — not a bespoke test hook.
  var audit = ClaimFlow.For(c).Audit;
  Assert.Equal(0, audit.Where(a => a.Kind == AuditKind.Reminded).Count());
}
```

## See also       {#see-also}
- [TestClock.Advance and TestClock.Set](https://osysharp.com/reference/testing/clock-advance/) — advance time so a reminder / deadline fires into the timeline
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — the slot whose reminders and refusals the timeline records
