Osy#betaRun it on Osyrin: osyrin.com

Reference / Security

The WorkQueues kit's floor — restricted queues, who may hold an item, and who moves whose work

policy WorksInbox => …; policy LeadsInbox => …; policy AdministersInbox => …; · Queue { Restricted = true } · TakeWork(item, expected)

What holds in every app on `Osysharp.WorkQueues`, whatever the app grants: an item in a restricted queue is invisible — in lists and in counts — to staff outside it; only somebody who may hold an item is ever given it; only a lead moves a colleague's work; the holder, queue, priority and promise are written only by the kit's run, and every change is recorded; a requester reads none of the service's internals; and queues and promises are the inbox admins'.

preview1 example compiled by CIsecuritykitinbox

Summary#

The WorkQueues kit's WorkItem is [Authority(Extensible)]: the app derives its kind (Ticket : WorkItem) and states that kind's grants, and the kit's denies are the floor every grant stands on. The app says three things — who works the inbox, who leads it, who administers it:

policy WorksInbox       => IsAgent;
policy LeadsInbox       => IsLead;
policy AdministersInbox => IsAdmin;

Signature#

invariantwhat holds
inbox-a-restricted-queue-is-read-by-its-members-onlyan item in a Restricted queue is unreadable to staff outside it — rows and counts; its holder and inbox admins read it, and the requester's read is the app's
inbox-work-goes-only-to-who-may-hold-itevery path that puts an item on a person — take, hand-on, a routing rule, the queue's distribution, a refill, an account offer, cover, follow-the-sun — asks MayHold: an active member of the item's queue (or on its organisation's account team, when the queue is not restricted) holding every skill it requires
inbox-assignment-only-through-the-runholder, queue, priority, escalation level and promise are written only by the kit's runs; every change is a Handover (and every escalation an Escalation)
inbox-moving-anothers-work-is-a-leadsunless AgentsHandOn, only the holder or a lead moves a held item
inbox-service-internals-are-staffsa requester reads no policy, budget, at-risk fraction, escalation level or history, assignment history, offer, clock record, queue or absence
inbox-a-count-is-the-viewers-ownevery count is the viewer's own read, never a privileged aggregate
inbox-a-view-grants-nothinga saved view only filters; a personal view is its owner's; shared views are the admins'
inbox-only-you-or-a-lead-set-your-availabilitya person's status, cover, ceiling, hours and absences are set by that person or a lead — nobody marks a colleague away and collects their work
inbox-cover-grants-no-accesscover moves an item only to somebody who may hold it — a restricted item, or one needing a skill the cover lacks, goes back to its queue; ActsFor is asked beside the app's own rule, so a delegate never acts on their own request
inbox-service-levels-are-the-adminsqueues (their distribution included), SLA policies and targets, routing rules, skills and shared views change only by an inbox admin

Description#

The app grants; the kit's floor holds#

Grants are never inherited, so a Ticket : WorkItem states its own reads and writes — staff work every ticket, a customer reads their own. Whatever it grants, the kit's denies hold: an app that grants its agents every ticket still cannot show one in a restricted queue to an agent outside it, and an app that grants update cannot write Assignee, Queue, Priority or the promise. Those change only through the kit's verbs, which raise events on the item's WorkHandling run.

The lead's verbs are their own events#

TakeWork, HandWorkTo and MoveWork raise an agent's event or a lead's, chosen by the caller's LeadsInbox; the lead's events are gated [Authorize(u => LeadsInbox)], so an agent cannot reach them by raising them herself. With AgentsHandOn off, an agent may take only unheld work and hand on only what she holds. With it on (the default), any agent may — the target must still be able to hold it.

Routing gives work only to who may hold it#

A routing rule's AssignTo, a queue's round robin or least loaded, and a refill all end in the same question the verbs ask. A rule naming somebody who may not hold the item gives it to nobody, and the item's timeline says so; a queue never gives an item to a member who lacks a skill it requires. Who holds which skills is a lead's to say (HeldSkill); which skills exist, which rules route and how a queue hands its work out are an inbox admin's.

Cover is not a way in#

When somebody goes away, each item they hold goes to their cover only when the cover may hold it; otherwise it goes back to its queue. So naming a colleague as cover never hands them a restricted queue's item, or work needing a skill they lack — and never into a read they did not have. Delegation reads ActsFor(u, p) ("u is p, or covers for p while p is away"), and it is always asked beside the app's own rule: an approver's delegate may approve, but never their own request (&& u != this.Item.Requester). The cover ends with the absence.

The collision sentence#

A take or hand-on carries expected — the holder the page showed. If somebody else holds the item now, the verb is refused with the sentence naming them ("Rahul took this just now."), and nothing moves. [ConcurrencyCheck] on WorkItem turns a truly simultaneous double-take into one success and one refusal instead of a lost update.

Known limit#

"Only the kit's run writes the holder" means any workflow body today: an app's own lifecycle body writes as the system too, so it could set Assignee past the kit's deny. Scoping that exemption to the package that declared the deny is an open platform decision (D-INBOX1, with D-WFRUNAS1).

An absolute due date#

WorkItem.DueAt is a promise no business-hours budget can say — "one calendar month from receipt". An app sets it when it creates the item; when set, it IS the resolution promise: counted in real time from ArrivedAt, never paused by a wait, at risk at AtRiskAt of that time and breached at the instant, recorded like any clock, and what the list counts down to while the resolution governs. It moves only through MoveDue(item, due, why), which is recorded on the item's trail. The Privacy kit's requests are the first user.

Examples#

A restricted queue, and the verbs that move work#

app SecureDesk {
  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 app grants its agents EVERY incident — and the kit's floor still keeps the Security queue's to its members.
entity Incident : WorkItem {
  security { allow read, create, update when WorksInbox; }
}

/// Open an incident straight into the restricted queue.
public Incident Report(string title) {
  return new Incident { Title = title, Queue = QueueNamed("Security") };
}

/// A lead hands it to a member of that queue — anyone else is refused, by name.
public void Assign(Incident incident, Principal to) { HandWorkTo(incident, to); }

See also#

Related

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…

The Organizations kit — companies, teams, and roles held within one

`Osysharp.Organizations` holds the groups people belong to — the companies an app's customers work for and the teams…

[Authority]

What an app that imports your entity may do to the `security { }` you declared on it. The default is `Narrowable` — the…

security { }

The rules that decide who may read and write an entity's rows. A where clause filters by the row (the owner sees their…