# The WorkQueues kit — queues, who holds what, and the service levels promised on any item

> `Osysharp.WorkQueues` puts any item an app derives from `WorkItem` — a ticket, an approval, a lead — into queues with holders and a promise. The app keeps its own states and tells the kit four facts as they happen (we answered, they wrote, we are waiting, it is done); the kit keeps the clocks — first response, every next response, resolution — in business hours, paused while waiting, recorded when breached, and retargeted when the priority changes.

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

## Summary        {#summary}
`use Osysharp.WorkQueues@1;` gives an app `WorkItem` to derive its kind of work from. The app keeps what the item **is** —
its states, fields and guards — and tells the kit four facts; the kit keeps the **time** and the **holder**:

```osy syntax
Responded(ticket);                      // we gave the requester a response
RequesterWrote(ticket);                 // they wrote — a response is owed again
WaitOn(ticket, WaitFor.Requester);      // we are waiting on someone (… WaitFor.Others, WaitFor.Pause) — ResumeWork ends it
WorkDone(ticket);                       // it is done (ReopenWork, CloseWork, CancelWork)
```

## Signature      {#signature}
```osy syntax
policy WorksInbox;  policy LeadsInbox;  policy AdministersInbox;          // the app supplies all three

[Authority(Extensible)] public entity WorkItem {
  string Title;  string Reference;  Queue? Queue;  Principal? Assignee;  WorkPriority Priority;  Handling Handling;
  Organization? Organization;  Principal? Requester;  string Language;  string Channel;  string Segment;  string Kind;
  DateTime ArrivedAt;   SlaPolicy? Policy;  TimeSpan? FirstResponseWithin;  …  SlaState Sla;
}

void Responded(WorkItem item, DateTime? at = null);
void RequesterWrote(WorkItem item, DateTime? at = null);
void WaitOn(WorkItem item, WaitFor reason, DateTime? at = null);    void ResumeWork(WorkItem item, DateTime? at = null);
void WorkDone(WorkItem item, DateTime? at = null);   void ReopenWork(…);   void CloseWork(…);   void CancelWork(WorkItem item, string why);
void Prioritise(WorkItem item, WorkPriority priority);
List<ItemClock> Clocks(WorkItem item);    bool HasPromise(WorkItem item);

void TakeWork(WorkItem item, Guid? expected = null);    void ReleaseWork(WorkItem item);
void HandWorkTo(WorkItem item, Principal person, Guid? expected = null);    void MoveWork(WorkItem item, Queue queue);
BulkResult TakeMany(List<WorkItem> items);    BulkResult MoveMany(List<WorkItem> items, Queue queue);
List<Handover> TrailOf(WorkItem item);
bool AddressWork(WorkItem item, Principal to);          // from a workflow's body only: give a kit's own item to a named person
int LoadOf(Principal person);    bool OnDuty(Principal person, DateTime now);    string NeedsOf(WorkItem item);

void AcceptOffer(WorkItem item);    void DeclineOffer(WorkItem item);    RoutingOffer? OfferOn(WorkItem item);
List<AccountRow> MyAccounts();
Absence GoAway(DateTime? until = null, Principal? cover = null, string reason = "");    void ComeBack();
Absence MarkAway(Principal person, DateTime? until = null, Principal? cover = null, string reason = "");    void MarkBack(Principal person);
policy ActsFor(Principal u, Principal? p);    // u is p, or covers for p while p is away
void EscalateWork(WorkItem item, string why);

List<WorkRow> Rows(BuiltInView view);   List<WorkRow> RowsIn(Queue queue);   List<WorkRow> RowsOf(InboxView view);
InboxCounts Counts();

app.Inbox = new InboxSetup { DefaultQueue = "Support", AgentsHandOn = true, AccountRouting = true,
                             KeepHolderOnEscalation = true, Routing = …, Views = …, Words = …, Links = … };

// What happens to an item is an EVENT the kit raises — each about the item, each saying itself (`e.Sentence()`, in the
// reader's language, when read). Automations, Webhooks and Activity take them as they are; an app hears one with [On].
ItemArrived · ItemQueued · ItemRouted · HolderChanged · ItemMoved · ItemEscalated · ItemOffered · OfferFellThrough
OfferWithdrawn
RoutingSkipped · ItemPooled · PickupOverdue · PriorityChanged · DueMoved · ItemCancelled · ItemReopened
PromiseAtRisk · PromiseBreached · PromiseMet · PromiseToldToHolder · PromiseToldToLeads · ItemLinked · ItemUnlinked
// Who should know is on the event: ItemOffered.To, HolderChanged.Told, PromiseToldToHolder.Holder — and, for a queue's
// leads, PickupOverdue and PromiseToldToLeads (all told in their bell by Osysharp.WorkQueues.Notifications).
List<Principal> InboxLeads(WorkItem item);              // its queue's members who satisfy LeadsInbox (else any queue's)
string InboxNotice(Event e);                            // what the person an event names reads ("Offered to you.")
bool IsInboxEvent(Event e);  DateTime InboxTime(Event e);   // for a timeline: one of these? when did it happen?
string InboxStepWords(InboxStep step);                  // a trail step's facts, said for the reader
List<TrailLine> TrailLines(WorkItem item);              // "why is this here", said for the reader (WhyHere shows it)
```

## Description    {#description}

### The app keeps its states; the kit keeps the time   {#facts}
One durable `WorkHandling` run per item, of **any** kind the app derives — beside the app's own lifecycle run, when it
has one (a `Spend : WorkItem` with its own approval workflow has two runs, and they agree because the app's workflow
calls the verbs from its states). The run's `Handling` is the phase the clocks read:

| `Handling` | after | resolution | response |
|---|---|---|---|
| `New` | it arrived | runs | **first response** runs |
| `Owed` | the requester wrote | runs | a **next response** runs — each its own cycle |
| `Answered` | we responded | runs | none |
| `WaitingOnRequester` | `WaitOn(…, WaitFor.Requester)` | **paused** | none |
| `WaitingOnOthers` | `WaitOn(…, WaitFor.Others)` | runs — unless the policy's `PauseWhileWaitingOnOthers`, then `Paused` | none |
| `Paused` | `WaitOn(…, WaitFor.Pause)` | paused | paused |
| `Done` | `WorkDone` | met; a reopen resumes it from where it stopped | none |

`Responded` and `Prioritise` are the team's (`WorksInbox`): a requester cannot stop the team's clock or raise their own
priority, and a timer — which is nobody — cannot say "we responded". The other facts may be said by anyone who can read
the item, and from a timer in the app's own lifecycle (a week of silence that solves a ticket says `WorkDone`).
`at:` replays an imported item's history as it happened; `ArrivedAt` is when its clocks start.

### The promise: the most specific policy wins   {#policy}
An `SlaPolicy` states conditions — `Priority`, the organisation's `Tier` (its own, else the default tier), `Queue`,
`Channel`, `Segment`, `Kind`, `ForVip` — and the `Hours` its clocks run in. Of the active policies whose **every**
stated condition matches, the one stating the most wins; ties go to the lower `Position`. Its `SlaTarget` rows (one per
`SlaMetric`) are snapshotted onto the item. An item no policy matches has **no promise**: `Clocks(item)` is empty, and a
screen says so rather than drawing a zero.

### Clocks: business time, paused, at risk, breached, retargeted   {#clocks}
Every clock runs in the policy's `ServiceHours` (holidays included; none: around the clock). Each is also kept as an
`SlaClock` record — when it started, the business time it used, when it went at risk, was met, was breached — which
outlives the run and is what reports read. A breach is **recorded and never fatal**: the item stays owed, and is met
late. `AtRiskAt` (default 0.75) is the fraction of a budget after which it is at risk — for every promise, the
resolution included (a reminder on the run's `Deadline`, measured on its own clock, [[workflow-remind#deadline]]). `Prioritise` chooses the policy
again and retargets every running clock, keeping the time already used. `WorkItem.Sla` is the worst running promise
(on track, at risk, breached) — a column, so a list and its counts can read it.

### Queues and who holds an item   {#holding}
An item waits in the queue the app named at create, else `InboxSetup.DefaultQueue` (made on first use). Only an active
`QueueMember` of its queue **may hold** it. `TakeWork(item, expected)` takes it — `expected` is who the page showed
holding it, so a take that lost the race is refused with the holder's name: "Rahul took this just now." A lead may take
or hand on anybody's work; an agent may only when `InboxSetup.AgentsHandOn` is set (the default). `MoveWork` moves it to
another queue — a member of that queue, or a lead — and the holder keeps it only where they may hold it. Every change of
holder or queue is a `Handover` row with its reason and who did it: `TrailOf(item)` is "why is this here". Each row
holds FACTS — from whom, to whom, by whom, which queue, why somebody could not take it and until when — never a
sentence; `TrailLines(item)` (and the `WhyHere` component) says them in the reader's language ([A history in the reader's language — keep what happened, say it when it is read](https://osysharp.com/reference/i18n/history/)).

### An item put to one person by name   {#addressee}
Some work is not for whoever is free: a question asked of one manager, an approval that is one person's to give.
Say who when the item is made — `new SpendApproval { Title = …, Addressee = manager }` — and it is theirs as it
arrives (`HandoverReason.Addressed`), without being routed and without using a turn of the queue's distribution.
Somebody who may not hold it (not a member of its queue) is not given it; it is then routed like any other item.
`Addressee` is said once, at create. A kit whose own run needs to pass such an item on — a request nobody answered
goes to the next person — says `AddressWork(item, to)` from its workflow's body; from a page or a plain function
that is refused, because a person hands work on with `HandWorkTo`, which asks whether they may.

### Who is given it: rules, distribution, capacity, skills   {#distribution}
When an item **arrives** it waits in the queue the app named, else the first matching `RoutingRule`'s, else the app's
`InboxRouting.QueueFor`, else the default queue. Rules are an inbox admin's rows, ordered by `Position`: conditions
(channel, words in the title, organisation, tier, VIP, language, segment, kind) and a **closed** set of actions — the
queue, the priority, a skill the item needs, a person to give it to (only when they may hold it). The first match stops
unless it says `ContinueAfter`. A rule may also carry the **app's own action**, by key (`AppAction = "billing"`): each
matching rule that has one is handed to `InboxRouting.Act(item, appAction, rule)` in the rules' order, so an admin's rule
can do what the kit has no word for — a helpdesk tags with it — while the kit's own set stays closed. (The settings page
shows the field in the app's words: `RoutingRuleSettings(appAction: "Tag it", appActionHint: "billing")`.) Anything else
the app does to every arrival is `InboxRouting.Routed`.

Then the queue's `Distribution`:

| `Distribution` | who is given it |
|---|---|
| `Manual` (default) | nobody — it waits in the pool, and `PickupWithin` tells the leads when nobody has taken it |
| `RoundRobin` | the free member whose turn it is — the one given something longest ago |
| `LeastLoaded` | the free member with the least work for their capacity; ties to the one given something longest ago |

**Free** means: may hold it (a member, holding every **required** skill), on duty (`Availability.Status` is
`Available` and their `Hours`, in their zone, are open — no row means available around the clock), and below their
**capacity** — the least of their own `Availability.Capacity`, their `QueueMember.Capacity` and the queue's
`DefaultCapacity`. **Load** counts only work that needs them: New, Owed, Answered, waiting on others — not waiting on
the requester, not done. Members holding more of the item's **preferred** skills are given it first, higher levels
first. Capacity limits what the inbox **gives**; a person may still take, or be handed, more.

An item's `Language` is a **preferred** skill (a `RoutingSkill` of kind `Language` whose `Code` matches its primary
subtag — "es" speaks to "es-MX"). For the queue's `LanguageWait` (10 minutes by default) only speakers are given it;
after that, anybody free — dedicated never means stuck.

Nobody free: the item waits in the pool, and is **pulled** the moment somebody is — their load drops (done, waiting on
the requester, released, handed on), or the language wait ends. A released item goes to whoever is free next, never
back to who let it go. Nothing polls: each pull is the run of the item that freed the capacity, raising `Route` on the
oldest-due pooled item. The trail says which: `RoundRobin`, `LeastLoaded`, `Rule`, `Refill`.

Two items given out at the same instant to the same person meet at that member's row (`[ConcurrencyCheck]`): the later
one's start is refused and redelivered, and is routed again with the first one counted.

### Account routing: the organisation's own people first   {#accounts}
An item of an organisation with an account team (the Accounts add-on's `AccountTeamMember`, [The Accounts add-on — the tier, entitlements, account team and VIPs of a customer organisation](https://osysharp.com/reference/security/organizations-accounts-kit/)) is **offered** to that team before
its queue hands it out:

```text
owner ─▶ backup (or the owner's cover) ─▶ the pod (least loaded first) ─▶ the segment queue (ForTier / ForSegment) ─▶ the queue
```

An offer is a `RoutingOffer` with its own durable run (`OfferFlow`). It waits the queue's `OfferFor` (15 minutes by
default) **in the queue's `Hours`**, so a dedicated owner on a business-hours queue gets an overnight item first thing in
the morning; then it falls through to the next step. Anybody who cannot take it **now** — away, off their own hours,
busy, offline, at capacity, or not allowed to hold it — is skipped at once, and the trail says why ("Mateo is away until
Mon 8 Jun 06:00 UTC — skipped."): dedicated never means stuck. The offered person sees it in `Rows(BuiltInView.Offered)`;
taking it (`AcceptOffer`, `TakeWork`) or **responding** to it accepts it, and `DeclineOffer` falls through at once.
Somebody else taking it withdraws the offer. The account team's own people may hold the item wherever it waits, unless
its queue is restricted. The item's clocks never wait for routing: account routing changes who gets it first, never
what was promised. `InboxSetup.AccountRouting = false` turns it off.

`MyAccounts()` lists the organisations whose account team the viewer is on, each with its open, at-risk, offered and
waiting work — one grouped read; `Rows(BuiltInView.MyAccounts)` is their work as a list.

### Away, cover, and follow-the-sun   {#away}
Routing reads what a person **said**, never a socket. A person is on duty when their `Availability.Status` is
`Available`, their own `Hours` (in their zone) are open, and they have no live `Absence`.

`GoAway(until, cover)` — or a lead's `MarkAway(person, until, cover)`, or an `Absence` planned ahead — hands the
person's held work over when it starts, by each queue's `OnAway` rule: `Keep`, `ToCover` (the default: to the cover
when the cover may hold the item, otherwise back to the queue, which gives it out again), or `ToQueue`. What is offered
to them falls through at once. At `Until` (or `ComeBack()`), the absence is over and they are given work again; what
cover took **stays** with the cover. `ActsFor(u, p)` — `u` is `p`, or covers for `p` while `p` is away — is what an
approval step asks, always beside its own rule: `[Authorize(u => ActsFor(u, this.Item.Approver) && u !=
this.Item.Requester)]`.

**Follow-the-sun:** halfway to the first-response promise, an item whose holder is off their hours goes to the least
loaded member of its queue who is on theirs (`HandoverReason.OffHoursCover`), unless the queue says
`CoverOffHours = false`.

### Escalation: levels, and what a promise does at its moments   {#escalation}
A queue has a `Level` (L1 = 1, L2 = 2, L3 = 3) and may name the queue it `EscalatesTo`. `EscalateWork(item, why)`
(the team's) moves the item there and raises its `EscalationLevel`: the promise is read again — a policy may be keyed
by queue — and every running clock is retargeted keeping the time used; the item is routed again at the new level, and
its holder keeps it only when they may hold it there (and `InboxSetup.KeepHolderOnEscalation`). Each escalation is an
`Escalation` row (from, to, by, why, and the promise that did it) and a `Handover` with reason `Escalated`.

A policy's `SlaEscalation` rows say what its clocks do at two moments — `AtRisk` and `Breached` — per promise
(`SlaMetric`). The actions are a **closed** set:

| `SlaAction` | does |
|---|---|
| `TellHolder` | raises `PromiseToldToHolder` (its `Holder` is told) |
| `TellLeads` | raises `PromiseToldToLeads` (its queue's leads are told — `InboxLeads`) |
| `RaisePriority` | one step up (Low → Normal → High → Urgent); the promise is read again and retargeted |
| `Escalate` | as `EscalateWork`, by the promise — `Escalation.By` is empty, `Metric` says which |
| `MoveTo` | to the row's `MoveTo` queue, the holder kept only where they may hold it |

Each fires **once per clock cycle** — the first response; each next response; the resolution — because the moment is
recorded once, on the clock. Anything richer (post in a channel, set a tag) is an automation's — on `PromiseAtRisk` or
`PromiseBreached`. Who is told is the app's composition: with `Osysharp.Notifications` and
`Osysharp.WorkQueues.Notifications`, the holder reads "First reply is at risk." in their bell with nothing written for it,
and so do the queue's leads ([Inbox.Notifications — the people the inbox names, told in their bell](https://osysharp.com/reference/workflow/inbox-notifications-kit/)).

### Who leads a queue   {#leads}
`InboxLeads(item)` answers the people who lead an item's queue: its active members who satisfy the app's `LeadsInbox` —
asked of each of them with [Security.Authorize — does THIS person satisfy a policy](https://osysharp.com/reference/security/authorize/), so the kit never needs to know how the app decides it — or, when
none of them does (or the item waits in no queue), everybody who leads in any queue, so an item in a queue nobody leads
still reaches somebody. Each person once; an account that cannot sign in is nobody's lead. It is what the leads' two
events are told to, and what an app hands its own "tell the leads" action.

### What a view holds, and how it is counted   {#views}
`Rows(BuiltInView.Mine | Offered | MyAccounts | Unassigned | Open | AtRisk | Waiting | Done)`, `RowsIn(queue)` and
`RowsOf(savedView)` are each a fixed handful of statements however long the list. `Counts()` is **one grouped read**
summed into every built-in view's count and every queue's — and, like every list, the viewer's own read: a count never
includes an item the viewer may not read. A saved `InboxView` filters by queue, holder, priority floor, tier,
organisation, at-risk, language, segment, kind and the app's own filter (`InboxViews.Narrow("tag:refund")`).

### Links between items: a closed set the app declares   {#links}
Two items are often about one thing — a request that repeats another, one caused by another, many reports of one
fault. The app declares the KINDS (`InboxSetup.Links`), each with its words from both sides; the kit keeps the link
(`WorkLink`), refuses one that makes no sense with a sentence, shows it on both items, and raises an event on both
(`ItemLinked`/`ItemUnlinked`, each saying itself — "Linked: Duplicate of #1042."):

```osy title="the desk's kinds of link" syntax
public class DeskLinks : InboxLinks {
  public override List<LinkKind> Kinds() {
    return [
      new LinkKind { Key = "related", Says = "Related to", Mutual = true },
      new LinkKind { Key = "duplicate", Says = "Duplicate of", Back = "Duplicated by", One = true },
      new LinkKind { Key = "incident", Says = "Incident of", Back = "Problem behind", One = true },
      new LinkKind { Key = "split", Says = "Split from", Back = "Split into", Offered = false },   // the app's code only
    ];
  }
}
app.Inbox = new InboxSetup { Links = new DeskLinks() };
```

- **`Link(from, to, key)`** / **`Unlink(link)`** — the verbs (no commit); **`LinkWork(item, other, key, back)`** /
  **`UnlinkWork(link)`** for a page (committed; `back`: the OTHER item is the one that says it). Refused: an undeclared
  kind, an item linked to itself, a link that already stands (either way, for a `Mutual` kind), a one-way link run
  backwards, a second link of a `One` kind, and a chain of one (linking to an item that itself links that way).
- **A link moves nothing** — no holder, queue or clock. What a kind MEANS is the app's: `LinkedFrom(problem,
  "incident")` lists the incidents its own "solved" can solve.
- **`ItemLinks(item, hrefOf, shortcut, empty)`** — the panel: every link from the item's side, live, each opening the
  other item (`hrefOf` builds the app's route from it), removable; "Link…" asks which way and finds the other item by
  its reference or words of its title (`FindWork`). A kind with `Offered = false` is shown and never chosen or removed.
- **The team's alone** (`inbox-links-are-staffs`): a requester never reads a link, nor makes or removes one.

## Examples       {#examples}

### A desk on the kit   {#example-desk}
```osy title="tickets in queues, with a promise, told the four facts" test app=workflow-inbox-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;

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

/// The desk's own kind of work. Its grants are its own: staff work every ticket; a customer reads theirs.
entity Ticket : WorkItem {
  [MaxLength(4000)] string Body = "";
  security {
    allow read, create, update when WorksInbox;
    allow read, create when IsAuthenticated where Requester == user;
  }
}

/// A reply goes out — and the inbox hears "we responded".
public void Reply(Ticket t, string text) {
  t.Body = t.Body + "\n\n" + text;
  Responded(t);
}

/// The customer wrote back — a response is owed again.
public void CustomerWrote(Ticket t, string text) {
  t.Body = t.Body + "\n\n" + text;
  RequesterWrote(t);
}

/// An admin's setup: money words go to Billing, and Billing hands its work out in turn, five at a time each.
public void SetUpBilling() {
  var billing = new Queue { Name = "Billing", Distribution = Distribution.RoundRobin, DefaultCapacity = 5 };
  new RoutingRule { Name = "Money words", TitleContains = "invoice|refund", ToQueue = billing };
}

/// An agent's holiday: their tickets go to the colleague covering, and they are given nothing until they are back.
public void Holiday(DateTime until, Principal cover) { GoAway(until, cover, "Holiday"); }

/// The accounts the viewer looks after, with their open and at-risk work.
public int AccountsAtRisk() {
  var atRisk = 0;
  foreach (var a in MyAccounts()) { atRisk = atRisk + a.AtRisk; }
  return atRisk;
}

/// An agent sends a ticket up a level — the team above it gets it, with the reason on its trail.
public void Escalate(Ticket t, string why) { EscalateWork(t, why); }

/// What an agent's "Mine" shows, and the rail's counts beside it.
public int MineCount() { return Rows(BuiltInView.Mine).Count; }
public int OpenCount() { return Counts().Open; }
```

### Hearing what happens to an item   {#example-events}
Every escalation is an `ItemEscalated` the kit raises. The desk keeps a line for each, with the kit's own sentence —
said again in the reader's language when the line is read — and the trigger needs nothing registered.

```osy title="an [On] handler on one of the inbox's events" test app=workflow-inbox-kit
using Osysharp.WorkQueues;

entity EscalationNote {
  [Required, MaxLength(300)] string Says;
  Ticket? Ticket;
  security { allow read, create when WorksInbox; }
}

[On]
void NoteTheEscalation(ItemEscalated e) {
  var about = Events.About(e);
  if (about is Ticket t) { new EscalationNote { Ticket = t, Says = e.Sentence() }; }
}
```

## See also       {#see-also}
- [The WorkQueues kit's floor — restricted queues, who may hold an item, and who moves whose work](https://osysharp.com/reference/security/inbox-kit/) — the floor every grant stands on: restricted queues, who may hold, who moves whose work
- [ServiceHours (SLA-accrual windows)](https://osysharp.com/reference/workflow/service-hours/) — the hours a promise runs in
- [Workflow.Retarget (re-base the SLA clocks)](https://osysharp.com/reference/workflow/retarget/) — what `Prioritise` does to the running clocks
- [Imported history — StartedAt, and raising an event at the time it happened](https://osysharp.com/reference/workflow/imported-history/) — `ArrivedAt` and `at:` for imported items
- [Workflow.WorkByItem&lt;T&gt; (one row per item — the board read)](https://osysharp.com/reference/workflow/work-by-item/) — the board read the lists are built on
- [The Presence kit — who else is on a record, and who moved it first](https://osysharp.com/reference/realtime/collaboration-kit/) — the "moved first" sentence, and seats on an item
- **`kits/work-queues/tests`** — the kit's front door: two kinds of work, the promises, queues, views, holding, the floor
