# Tracks and Initial (the field a workflow drives)

> Names the enum field a workflow owns and the state a run starts in. No application code may write that field, and when the workflow autostarts it also supplies the field's starting value, so the entity declares no default.

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

## Summary        {#summary}
A workflow drives one field on one entity: the field that says where a row has got to. **`Tracks`** names that field
and **`Initial`** names the state a fresh run begins in.

Together they hand the field over. From that point it is the workflow's, not the application's — it moves only by
transition, and reading it anywhere tells you the truth about the run. When the workflow starts with the row, it
supplies the field's opening value too.

## Signature      {#signature}
```osy syntax
workflow <Name> {
  Tracks  = <Entity>.<Property>;
  Initial = <State>;
  …
}
```
The property is an enum-typed member of the tracked entity, and every state and terminal in the workflow must be a
member of that enum — so the field's type *is* the workflow's vocabulary of states.

## Description    {#description}
### When the workflow starts with the row, the field needs no default   {#autostart}
An `Autostart = true` workflow begins the moment its row exists, so `Initial` *is* the field's value from the start
and the entity does not restate it:

```osy title="the field is declared bare — the workflow supplies its starting value" test app=workflow-tracks
enum ApprovalStage { Pending, Approved, Rejected }

entity Invoice {
  [Required] [MaxLength(120)] string Description;
  [Required] decimal Amount;
  ApprovalStage Stage;
  security { allow create, read, update when IsAuthenticated || IsAnonymous; }
}

workflow ExpenseApproval {
  Tracks    = Invoice.Stage;
  Autostart = true;
  Initial   = Pending;

  event Decide(bool approved);

  state Pending {
    subscribe Decide(bool approved);
    on Decide(bool approved) {
      when (approved) { goto Approved; }
      default { goto Rejected; }
    }
  }

  terminal success Approved { }
  terminal error   Rejected { Message = "rejected"; }
}
```

A new `Invoice` reads back `Pending` with nothing in the source saying so twice.

This matters more than it looks. Elsewhere in the language a bare enum member is **required** — the platform will not
invent a value for it, because the first member of an enum is a position, not a decision, and reordering the members
would silently change what every new row means. A tracked field is the one case where a real value is genuinely
available: the workflow declared it.

### WHEN does it start, exactly?   {#autostart-timing}
"Begins the moment its row exists" is precise, not a loose way of saying "soon": an `Autostart = true` workflow's
initial `enter{}` runs **synchronously, inside the same commit** that created the row — before the next statement
after `UnitOfWork.Commit()` executes, in the SAME function, with no pump or wait of any kind. A read immediately
after that commit already sees whatever the `enter{}` body wrote:

```osy title="the enter body has already run by the time the NEXT function looks" run app=workflow-tracks-autostart-timing
enum TicketState { Open, Closed }

entity Ticket {
  [MaxLength(80)] string Title;
  TicketState Status;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

entity AuditEntry {
  [Required, MaxLength(80)] string Note;
  security { allow read, create when IsAnonymous || IsAuthenticated; }
}

workflow TicketFlow {
  Tracks = Ticket.Status;
  Autostart = true;
  Initial = Open;
  event Close();
  state Open {
    enter { new AuditEntry { Note = "opened" }; }
    on Close { goto Closed; }
  }
  terminal success Closed { }
}

[Test]
void AutostartedEnterBody_RunsInTheSameCommit_NoPumpNeeded() {
  new Ticket { Title = "t" };
  UnitOfWork.Commit();

  // No `Workflow.Settle(...)` here — the enter{} body has already run, in the commit above.
  Assert.Equal(1, AuditEntry.Count());
}
```

⚠ **This is a deliberate platform guarantee, not an implementation detail that happens to hold today.** In
production, autostart is ALSO delivered by a durable `wf-start` job the platform enqueues at commit — the backstop
for a row committed by a path that is not Osy# (a REST write, an import) and for recovering a start this process
died during. But an Osy# `UnitOfWork.Commit()` drives the autostart pass itself before returning, specifically so
"create a row, then ask about it" reads the way it looks. Starting the same run twice is a no-op by design, so the
durable job racing (or duplicating) the in-process start is harmless.

**`Workflow.Settle(entity)` is a TEST-ONLY primitive** for a different problem: draining DUE, ALREADY-SCHEDULED work
— timers, reminders, a milestone's deadline — deterministically, without a real clock to wait on. It has no
counterpart in production code, where that same work is drained by the durable dispatcher on its own schedule. Reach
for it in a test that needs to observe a timer fire or a deadline expire; an autostarted `enter{}` needs no `Settle`
at all, because it has already run by the time `UnitOfWork.Commit()` returns.

**A caller with no Osy# session can still trigger an autostart** — a webhook, a scheduled tick, a REST write — just
on the durable job's timing rather than in the same instant: the row lands, the write's own commit finishes (with no
run attached yet), and the durable `wf-start` job picks it up and starts the run shortly after. What is guaranteed
synchronous is specifically an Osy# `UnitOfWork.Commit()`; every other write path gets the same eventual guarantee,
on the dispatcher's cadence rather than in-line.

### Restating it is refused   {#no-restate}
Writing the default anyway is a compile error, not a redundancy the compiler tolerates:

```osy title="refused — the workflow already supplies it" syntax
ApprovalStage Stage = ApprovalStage.Pending;   // 'Stage' on 'Invoice' is owned by workflow 'ExpenseApproval' …
```

The reason is drift. Two copies of one fact stay in step only while someone keeps them there, and the copy a reader
believes is the one in the entity — so the day `Initial` changes and the declaration does not, the source says
something false and nothing reports it. One statement of the fact cannot disagree with itself.

### The field is read-only to application code   {#read-only}
No function, action, or object initializer may assign a tracked field; a workflow's own handlers may. This is the
same ownership seen from the other side — if application code could set the field, the state would no longer mean
"where this run has got to", it would mean "whatever was written last".

```osy title="the state moves by transition, never by assignment" syntax
invoice.Stage = ApprovalStage.Approved;   // refused — the workflow drives it
```

To move a run, send it an event and let a route transition it.

### When the run starts later, the field's earlier value is yours to state   {#late-start}
`Initial` is where the **run** begins, not where the **row** begins. Those are the same moment only when the workflow
autostarts. If it does not — `Autostart = false`, or a condition that is not yet true — the row exists for a while
with no run attached, and what the field reads in that window is a fact only you know:

```osy title="a report is a draft before it is anyone else's problem" test app=workflow-tracks-draft
enum ReportStage { Draft, InApproval, Approved, Rejected }

entity Report {
  [Required] [MaxLength(120)] string Title;
  DateTime? SubmittedAt;
  ReportStage Stage = ReportStage.Draft;
  security { allow create, read, update when IsAuthenticated || IsAnonymous; }
}

workflow ReportApproval {
  Tracks    = Report.Stage;
  Autostart = this.Item.SubmittedAt != null;   // starts on submit, not on create
  Initial   = InApproval;

  event Decide(bool approved);

  state InApproval {
    subscribe Decide(bool approved);
    on Decide(bool approved) {
      when (approved) { goto Approved; }
      default { goto Rejected; }
    }
  }

  terminal success Approved { }
  terminal error   Rejected { Message = "rejected"; }
}
```

`Draft` is a member of the tracked enum but not a state the workflow ever occupies — it is where a report sits before
the workflow has anything to do. Here the default is required, and it is not refused: the workflow is not claiming to
supply it.

**So the question "does my tracked field need a default?" has one answer: does the workflow start when the row is
created?** If yes, the workflow supplies the value and you must not restate it. If no, it is yours.

## Examples       {#examples}
An `Initial` that is decided per row rather than fixed — the field then starts wherever the expression lands when the
run begins, and a declared default is legal for the same reason as above:

```osy title="a starting state that depends on the row" syntax
workflow ExpenseApproval {
  Tracks  = Invoice.Stage;
  Initial = this.Item.Amount > 500 ? ApprovalStage.Pending : ApprovalStage.Approved;
  …
}
```

## Notes          {#notes}
**Every state must be a member of the tracked enum.** A state the enum does not name is a compile error — the two
declarations are one vocabulary, so they cannot drift apart either.

**`Autostart` decides when a run begins** — and therefore whether the field's opening value is the workflow's to
supply or yours to declare. That is the one thing to carry away from this page.

## See also       {#see-also}
- [subscribe](https://osysharp.com/reference/workflow/subscribe/) — declaring what a state waits for
- [complete when (a state's own completion condition)](https://osysharp.com/reference/workflow/complete-when/) — leaving a state on a condition rather than an event
- [Workflow.Run (start a workflow)](https://osysharp.com/reference/workflow/run/) — starting child work from a state
