# Give any entity a lifecycle from a kit

> A kit can give an entity the app has not written yet a whole lifecycle: an Extensible base the app's own type derives from, a kit workflow that tracks one field of that base and starts for every subtype, the kit's events as the only way in (with their security on the events), and the engine's deadlines, reminders and service hours as the business rules. The app keeps its own workflow beside the kit's on the same item — one autostart per column, which the compiler enforces. The WorkQueues kit is built this way.

<!-- id: workflow-lifecycle-from-a-kit · area: workflow · stability: preview · html: https://osysharp.com/reference/workflow/lifecycle-from-a-kit/ -->

## Summary        {#summary}
Some behaviour is the same for every app that has it: a support ticket, a purchase request and a job candidacy all wait
in a queue, are held by somebody, and owe somebody an answer by a time. A kit can own that behaviour — states,
timers, rules and all — for an entity it has never seen, because the app's entity **derives from the kit's base**:

```osy syntax
entity Ticket : WorkItem { [MaxLength(4000)] string Body = ""; … }   // the app's type; the kit's lifecycle runs on it
```

The kit's workflow tracks a field of `WorkItem`, starts for every `Ticket` the moment one is created, and is moved only
by the kit's own events. The app's own workflow — the ticket's stages — runs beside it on the same row.

## Signature      {#signature}
```osy syntax
// ── in the kit ──
[Authority(Extensible)]                          // the app derives its own types from it
public entity WorkItem {
  Handling Handling;                             // the ONE field the kit's workflow tracks — no default, the run owns it
  Principal? Assignee;                           // …and the facts its rules keep
  security { deny update Assignee, Handling; }   // the floor: app code never writes what the run keeps
}

public workflow WorkHandling {
  Tracks    = WorkItem.Handling;                 // a field of the BASE: the run starts for every subtype's rows
  Autostart = true;
  Initial   = New;
  ServiceHours = this.Item.Hours;                // business rules are the engine's — hours, deadlines, reminders
  Deadline { Within = this.Item.ResolutionWithin; Remind AtRisk(After = Within * 0.75) { … } }
  [Authorize(u => WorksInbox)] event Respond();  // the ways in, each with its own security
  …
}

// The kit's verbs — what the app calls. Each raises one event on the item's own run.
public void Responded(WorkItem item) { WorkHandling.RaiseRespond(item); }

// ── in the app ──
entity Ticket : WorkItem { … }                   // joins the lifecycle by deriving
workflow TicketLife { Tracks = Ticket.Stage; Autostart = true; … }   // its OWN states, beside the kit's
```

## Description    {#description}

### 1. An Extensible base the app derives from   {#base}
`[Authority(Extensible)]` says the app may derive its own types from the kit's entity. The app's type keeps everything
it IS — its fields, its states, its own grants (grants are never inherited: the app says who reads a `Ticket`) — and
gains the base's fields, which are what the kit reasons about. The base's **denies** are the floor every subtype stands
on, whatever the app grants: the Inbox's `deny update Assignee, Queue, Priority …` holds against app code on a `Ticket`
just as on any other kind of work.

### 2. A kit workflow tracking one field, autostarting per subtype   {#tracks}
A workflow that `Tracks` a field of the base covers every subtype: it autostarts when a `Ticket` (or a `Spend`, or a
`Candidacy`) is committed, and `Workflow.WorkByItem<WorkItem>()` lists them all on one board while
`Workflow.WorkByItem<Ticket>()` lists only tickets. The tracked field has no default — the run's `Initial` state is its
first value.

### 3. The kit's events are the only way in   {#events}
The app never writes the tracked field or the facts the run keeps; it calls the kit's verbs (`Responded(item)`,
`WaitOn(item, WaitFor.Requester)`, `TakeWork(item, expected)`), each of which raises one event on the item's run. The
security is on the EVENT — `[Authorize(u => WorksInbox)] event Respond();` — so "only the team says it responded" is a
gate on the way in, not a check somebody might skip, and a raise from a workflow body carries the actor who drove it
([Raising a workflow event](https://osysharp.com/reference/workflow/raise/)). A run body writes as the system, so it can keep what the base denies to app code.

### 4. Deadlines, reminders and service hours are the business rules   {#rules}
The promise is the engine's, not a polling job: the run's `Deadline` is the resolution clock, a slot's milestones are
the response clocks, `Remind … (After = Within * 0.75)` is "at risk", and `ServiceHours = this.Item.Hours` makes all of
them count working time only, paused in the states that do not accrue. `Workflow.Retarget()` moves every running clock
when the promise changes, keeping the time already used.

### 5. The app's own workflow, beside the kit's   {#beside}
An item may carry two runs: the kit's (tracking `Handling`) and the app's (tracking its own `Stage`). The app's states
call the kit's verbs as they happen — "solved" is `WorkDone(this.Item)`, "pending" is `WaitOn(this.Item,
WaitFor.Requester)` — and declare no clock of their own. **One autostart per column**: a second workflow that autostarts
over the same field anywhere in the hierarchy is refused at compile, naming both, so two machines can never fight over
one value.

```osy syntax
workflow Rival { Tracks = Ticket.Handling; Autostart = true; … }
// ✗ 'Rival' autostarts over Handling, which WorkHandling (Osysharp.WorkQueues) already autostarts over — one machine per column.
```

### When to reach for this   {#when}
When the lifecycle is the same in every app that has it and the app's item differs: inbox work, approvals, privacy
requests, invitations, dunning, review and signing. When the states ARE the app's (an order's fulfilment), they stay in
the app's own workflow.

## Examples       {#examples}

### A ticket with its own stages, on the Inbox's lifecycle   {#example-ticket}
```osy title="the app's stages beside the kit's clocks — two runs, one ticket" test app=workflow-lifecycle-from-a-kit
app Desk {
  use Osysharp.Identity@0;
  use Osysharp.Organizations@0;
  use Osysharp.Organizations.ServicePlans@0;
  use Osysharp.Presence@0;
  use Osysharp.Observability;
  use Osysharp.WorkQueues@1;
  use Osysharp.Workflow;
  use Osysharp.ServiceHours;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.WorkQueues;
using Osysharp.Workflow;

[Role] enum DeskRole { Authenticator, Agent, Admin }
policy WorksInbox => RoleGrant.Any(g => g.Member == user && (g.Role == DeskRole.Agent || g.Role == DeskRole.Admin));
policy LeadsInbox => RoleGrant.Any(g => g.Member == user && g.Role == DeskRole.Admin);
policy AdministersInbox => RoleGrant.Any(g => g.Member == user && g.Role == DeskRole.Admin);
policy ManagesOrganizations => AdministersInbox;
policy CollaboratesOnRecords => WorksInbox;

enum Stage { Open, Pending, Solved }

/// The app's own kind of work — it joins the Inbox's lifecycle by deriving from WorkItem.
entity Ticket : WorkItem {
  [MaxLength(4000)] string Body = "";
  Stage Stage;                                   // no default — TicketLife owns it
  security { allow read, create, update when WorksInbox; }
}

/// The ticket's OWN stages, beside the kit's WorkHandling. It tells the inbox the facts as they happen and keeps no
/// clock of its own: the promise, its hours and its breaches are the kit's.
workflow TicketLife {
  Tracks    = Ticket.Stage;
  Autostart = true;
  Initial   = Open;
  [Authorize(u => WorksInbox)] event AskCustomer();
  [Authorize(u => WorksInbox)] event Solve();
  event CustomerReplied();
  state Open {
    on AskCustomer { Responded(this.Item); goto Pending; }
    on Solve { Responded(this.Item); goto Solved; }
  }
  state Pending {
    enter { WaitOn(this.Item, WaitFor.Requester); }
    on CustomerReplied { RequesterWrote(this.Item); goto Open; }
    on Solve { goto Solved; }
  }
  terminal success Solved { enter { WorkDone(this.Item); } }
}
```

## See also       {#see-also}
- [The WorkQueues kit — queues, who holds what, and the service levels promised on any item](https://osysharp.com/reference/workflow/inbox-kit/) — the Inbox, built this way: queues, holders, promises over any `WorkItem`
- [entity Sub : Base](https://osysharp.com/reference/entity/inheritance/) — Extensible bases, subtypes, and "a workflow over a base covers its subtypes"
- [Workflow.WorkByItem&lt;T&gt; (one row per item — the board read)](https://osysharp.com/reference/workflow/work-by-item/) — one board across an item's runs
- [ServiceHours (SLA-accrual windows)](https://osysharp.com/reference/workflow/service-hours/) · [Remind (milestone reminders)](https://osysharp.com/reference/workflow/remind/) — the business rules the engine keeps
- [Raising a workflow event](https://osysharp.com/reference/workflow/raise/) — raising a kit's event from a body, and who the actor is
