@xema/omni-protocol 0.1.90 → 0.1.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1257,10 +1257,10 @@ export interface BreakRequest {
1257
1257
  * A break placed on the agent rather than requested by them.
1258
1258
  *
1259
1259
  * `by` is required in both arms. Who put somebody off the floor survives whether or not the
1260
- * break ends on a clock -- an imposed break with no origin is a state the agent cannot reason
1260
+ * break ends on a clock -- a forced break with no origin is a state the agent cannot reason
1261
1261
  * about, and one that ends on a condition is still somebody's decision.
1262
1262
  */
1263
- export type ImposedBreak = {
1263
+ export type ForcedBreak = {
1264
1264
  by: UserId;
1265
1265
  endsAutomatically: true;
1266
1266
  endsAt: IsoTimestamp;
@@ -1279,9 +1279,9 @@ export interface BreakState {
1279
1279
  retryAfterMs?: number;
1280
1280
  /** Not-ready codes this provider offers. Omitted when it defines none. */
1281
1281
  reasons?: BreakReason[];
1282
- /** Which reason the current break is on, a published `BreakReason.id`. Omitted when there is no break; required on a break in effect or starting after the task where the provider publishes `reasons`, an imposed one included. */
1282
+ /** Which reason the current break is on, a published `BreakReason.id`. Omitted when there is no break; required on a break in effect or starting after the task where the provider publishes `reasons`, a forced one included. */
1283
1283
  activeReasonId?: string;
1284
- imposed?: ImposedBreak;
1284
+ forced?: ForcedBreak;
1285
1285
  }
1286
1286
  /** Capacity is a statement, not a request: it is taken, never refused. A provider that cannot carry the count allocates within what it can and says so on a `diagnostic`. */
1287
1287
  export type CapacityResult = {
@@ -1398,7 +1398,7 @@ export type TeamBreakCommand = {
1398
1398
  type: "policy";
1399
1399
  policy: "ask" | "auto-approve" | "suspended";
1400
1400
  }
1401
- /** `reasonId` names a published `BreakReason.id`, required whenever the provider publishes reasons: the member's imposed break carries it as `activeReasonId`. */
1401
+ /** `reasonId` names a published `BreakReason.id`, required whenever the provider publishes reasons: the member's forced break carries it as `activeReasonId`. */
1402
1402
  | {
1403
1403
  type: "place";
1404
1404
  memberId: UserId;
@@ -1692,7 +1692,7 @@ export interface Connection<C extends Channel = Channel> {
1692
1692
  setCapacity(capacity: AgentCapacity): Promise<CapacityResult>;
1693
1693
  execute(request: TaskCommandRequest<C>): Promise<TaskCommandResult>;
1694
1694
  disconnect(): Promise<void>;
1695
- /** Required of any adapter publishing a `UserId` -- on an imposed break, a team member list, or history. */
1695
+ /** Required of any adapter publishing a `UserId` -- on a forced break, a team member list, or history. */
1696
1696
  describeUsers?(ids: UserId[]): Promise<User[]>;
1697
1697
  /** Required when the manifest declares `idleCapabilities.dial`. */
1698
1698
  dial?(request: DialRequest): Promise<DialResult>;
package/dist/testing.d.ts CHANGED
@@ -7,7 +7,7 @@ export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation }
7
7
  * rules -- each optional part of a task, each optional part of the break state and team member list, each
8
8
  * declared contribution, and each event type.
9
9
  */
10
- declare const STATE_SUBJECTS: readonly ["tasks", "task.browsers", "task.attributes", "task.interactionHistory", "task.onCall", "task.leadAssist", "task.assisting", "task.listening", "task.media", "task.acceptance", "task.outcomes", "task.destinations", "task.custom", "task.locked", "break.reasons", "break.imposed", "team.members", "team.requests", "contacts", "scheduledActivities", "team.policies"];
10
+ declare const STATE_SUBJECTS: readonly ["tasks", "task.browsers", "task.attributes", "task.interactionHistory", "task.onCall", "task.leadAssist", "task.assisting", "task.listening", "task.media", "task.acceptance", "task.outcomes", "task.destinations", "task.custom", "task.locked", "break.reasons", "break.forced", "team.members", "team.requests", "contacts", "scheduledActivities", "team.policies"];
11
11
  export type ContractSubject = (typeof STATE_SUBJECTS)[number] | `event.${ProviderEvent["type"]}`;
12
12
  export interface AdapterContractResult {
13
13
  events: ProviderEventEnvelope[];
@@ -202,7 +202,7 @@ export declare class BreakStream {
202
202
  /**
203
203
  * A break follows its requests. Given a provider's stream -- seeded with its initial snapshot,
204
204
  * either supplied separately or carried by a snapshot event -- every `break-state` moves the way the guide describes: a commit's states only
205
- * after a grant, never backwards, and a break placed on the agent arriving in effect with `imposed`.
205
+ * after a grant, never backwards, and a break placed on the agent arriving in effect with `forced`.
206
206
  */
207
207
  export declare function assertBreakFollowsItsRequests(envelopes: readonly ProviderEventEnvelope[], snapshot?: Snapshot): void;
208
208
  /**
package/dist/testing.js CHANGED
@@ -23,7 +23,7 @@ const STATE_SUBJECTS = [
23
23
  "task.custom",
24
24
  "task.locked",
25
25
  "break.reasons",
26
- "break.imposed",
26
+ "break.forced",
27
27
  "team.members",
28
28
  "team.requests",
29
29
  "contacts",
@@ -85,8 +85,8 @@ function observeBreak(value, seen) {
85
85
  return;
86
86
  if (some(value.reasons))
87
87
  seen.add("break.reasons");
88
- if (value.imposed !== undefined)
89
- seen.add("break.imposed");
88
+ if (value.forced !== undefined)
89
+ seen.add("break.forced");
90
90
  }
91
91
  function observeTeam(value, seen) {
92
92
  if (!isRecord(value))
@@ -636,7 +636,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
636
636
  * its shape.
637
637
  */
638
638
  function publishesUserIds(snapshot) {
639
- if (snapshot?.break?.imposed?.by !== undefined)
639
+ if (snapshot?.break?.forced?.by !== undefined)
640
640
  return true;
641
641
  if (teamNamesUsers(snapshot?.team))
642
642
  return true;
@@ -644,12 +644,12 @@ function publishesUserIds(snapshot) {
644
644
  return false;
645
645
  return snapshot.tasks.some(taskNamesUsers);
646
646
  }
647
- /** Every UserId a snapshot publishes: on the record, the room, a lead request, an imposed break, the team member list. */
647
+ /** Every UserId a snapshot publishes: on the record, the room, a lead request, a forced break, the team member list. */
648
648
  function userIdsIn(snapshot) {
649
649
  const ids = new Set();
650
650
  const add = (value) => { if (typeof value === "string" && value.length > 0)
651
651
  ids.add(value); };
652
- add(snapshot?.break?.imposed?.by);
652
+ add(snapshot?.break?.forced?.by);
653
653
  const team = snapshot?.team;
654
654
  if (isRecord(team)) {
655
655
  for (const member of Array.isArray(team.members) ? team.members : [])
@@ -692,7 +692,7 @@ function eventNamesUsers(envelope) {
692
692
  return false;
693
693
  switch (event.type) {
694
694
  case "snapshot": return publishesUserIds(event.snapshot);
695
- case "break-state": return isRecord(event.break) && isRecord(event.break.imposed) && event.break.imposed.by !== undefined;
695
+ case "break-state": return isRecord(event.break) && isRecord(event.break.forced) && event.break.forced.by !== undefined;
696
696
  case "task-offered":
697
697
  case "task-updated": return taskNamesUsers(event.task);
698
698
  case "team-updated": return teamNamesUsers(event.team);
@@ -1868,7 +1868,7 @@ async function driveOneCall(drive) {
1868
1868
  // A request goes not-requested -> awaiting-decision | granted; a commit goes granted ->
1869
1869
  // starting-after-task | in-effect; work ending goes starting-after-task -> in-effect; a denial,
1870
1870
  // a cancel, an end or a release goes back to not-requested; a placed break arrives in-effect
1871
- // with `imposed`. Nothing else is a move the guide describes.
1871
+ // with `forced`. Nothing else is a move the guide describes.
1872
1872
  /** What a stream has said about the agent's break, and the moves it may not make. */
1873
1873
  export class BreakStream {
1874
1874
  state;
@@ -1914,7 +1914,7 @@ export class BreakStream {
1914
1914
  /**
1915
1915
  * A break follows its requests. Given a provider's stream -- seeded with its initial snapshot,
1916
1916
  * either supplied separately or carried by a snapshot event -- every `break-state` moves the way the guide describes: a commit's states only
1917
- * after a grant, never backwards, and a break placed on the agent arriving in effect with `imposed`.
1917
+ * after a grant, never backwards, and a break placed on the agent arriving in effect with `forced`.
1918
1918
  */
1919
1919
  export function assertBreakFollowsItsRequests(envelopes, snapshot) {
1920
1920
  const stream = new BreakStream();
@@ -1293,23 +1293,23 @@ function validateTaskInto(task, context, path, into) {
1293
1293
  // ---------------------------------------------------------------------------
1294
1294
  // Breaks, team, snapshot.
1295
1295
  // ---------------------------------------------------------------------------
1296
- function validateImposedBreak(value, path, into) {
1296
+ function validateForcedBreak(value, path, into) {
1297
1297
  if (!isPlainObject(value)) {
1298
- into.add("break.imposed.shape", path, "an imposed break must be an object");
1298
+ into.add("break.forced.shape", path, "a forced break must be an object");
1299
1299
  return;
1300
1300
  }
1301
1301
  // `by` is required either way. Who put somebody off the floor survives whether or not the
1302
- // break ends on a clock -- an imposed break with no origin is a state the agent cannot
1302
+ // break ends on a clock -- a forced break with no origin is a state the agent cannot
1303
1303
  // reason about.
1304
- into.require(isUserId(value.by), "break.imposed.by", `${path}.by`, "an imposed break must say who placed it");
1304
+ into.require(isUserId(value.by), "break.forced.by", `${path}.by`, "a forced break must say who placed it");
1305
1305
  if (value.endsAutomatically === true) {
1306
- into.timestamp(value.endsAt, "break.imposed.endsAt", `${path}.endsAt`);
1306
+ into.timestamp(value.endsAt, "break.forced.endsAt", `${path}.endsAt`);
1307
1307
  }
1308
1308
  else if (value.endsAutomatically === false) {
1309
- into.require(value.endsAt === undefined, "break.imposed.endsAt.unexpected", `${path}.endsAt`, "a break that does not end automatically must not carry an end time");
1309
+ into.require(value.endsAt === undefined, "break.forced.endsAt.unexpected", `${path}.endsAt`, "a break that does not end automatically must not carry an end time");
1310
1310
  }
1311
1311
  else {
1312
- into.add("break.imposed.endsAutomatically", `${path}.endsAutomatically`, "an imposed break must say whether it ends automatically");
1312
+ into.add("break.forced.endsAutomatically", `${path}.endsAutomatically`, "a forced break must say whether it ends automatically");
1313
1313
  }
1314
1314
  }
1315
1315
  /** Checks break status against the full currently retained task set, after a transaction. */
@@ -1398,7 +1398,7 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
1398
1398
  validateBreakState(context.memberBreak, `${path}.memberBreak`, into);
1399
1399
  const state = isPlainObject(context.memberBreak) ? context.memberBreak : {};
1400
1400
  if (command.type === "release")
1401
- into.require(state.imposed !== undefined && (state.approval === "in-effect" || state.approval === "starting-after-task"), "team.break.command.release", path, "release a currently imposed break");
1401
+ into.require(state.forced !== undefined && (state.approval === "in-effect" || state.approval === "starting-after-task"), "team.break.command.release", path, "release a currently forced break");
1402
1402
  if (command.type === "place") {
1403
1403
  if (command.reasonId !== undefined)
1404
1404
  into.filled(command.reasonId, "team.break.command.reasonId", path, "reasonId must not be empty");
@@ -1459,7 +1459,7 @@ export function validateBreakCommand(method, request, state, context, path = "br
1459
1459
  into.require(approval === "awaiting-decision" || approval === "granted", "break.command.cancel.precommit", path, "cancel only a pre-commit request; a raced commit requires commit recovery");
1460
1460
  if (method === "endBreak") {
1461
1461
  into.require(approval === "starting-after-task" || approval === "in-effect", "break.command.end.started", path, "end a committed break, including a returning provider still finishing work");
1462
- into.require(state.imposed === undefined, "break.command.end.imposed", path, "an agent cannot end an imposed break; an authorized lead releases it");
1462
+ into.require(state.forced === undefined, "break.command.end.forced", path, "an agent cannot end a forced break; an authorized lead releases it");
1463
1463
  }
1464
1464
  }
1465
1465
  return into.violations;
@@ -1480,7 +1480,7 @@ export function validateBreakTransition(before, after, path = "break") {
1480
1480
  const from = before.approval;
1481
1481
  const to = after.approval;
1482
1482
  const committed = to === "starting-after-task" || to === "in-effect";
1483
- if (committed && (from === "not-requested" || from === "awaiting-decision") && after.imposed === undefined) {
1483
+ if (committed && (from === "not-requested" || from === "awaiting-decision") && after.forced === undefined) {
1484
1484
  into.add("stream.breakState.commitBeforeGrant", `${path}.after.approval`, `${to} follows a commit, and a commit follows granted; the break stood at ${from}`);
1485
1485
  }
1486
1486
  const backwards = (from === "in-effect" && (to === "awaiting-decision" || to === "granted" || to === "starting-after-task")) ||
@@ -1495,6 +1495,7 @@ function validateBreakState(value, path, into) {
1495
1495
  into.add("break.shape", path, "break state must be an object");
1496
1496
  return;
1497
1497
  }
1498
+ into.require(!Object.hasOwn(value, "imposed"), "break.forced.renamed", `${path}.imposed`, "imposed was renamed to forced; use only forced");
1498
1499
  into.oneOf(value.approval, BREAK_APPROVALS, "break.approval", `${path}.approval`);
1499
1500
  into.require(typeof value.mayAsk === "boolean", "break.mayAsk", `${path}.mayAsk`, "mayAsk says whether the agent may ask for a break: a boolean");
1500
1501
  for (const field of ["refusedReason", "decisionReason"]) {
@@ -1515,12 +1516,12 @@ function validateBreakState(value, path, into) {
1515
1516
  // break that is not happening.
1516
1517
  into.require(value.approval !== "not-requested", "break.activeReasonId.approval", `${path}.activeReasonId`, "activeReasonId must be omitted when no break is requested or in effect");
1517
1518
  }
1518
- if (value.imposed !== undefined) {
1519
- validateImposedBreak(value.imposed, `${path}.imposed`, into);
1520
- // An imposed break is a break somebody placed; beside `not-requested` there is no break.
1521
- // An imposed break is a break in progress or about to be: it travels with in-effect or
1519
+ if (value.forced !== undefined) {
1520
+ validateForcedBreak(value.forced, `${path}.forced`, into);
1521
+ // A forced break is a break somebody placed; beside `not-requested` there is no break.
1522
+ // A forced break is a break in progress or about to be: it travels with in-effect or
1522
1523
  // starting-after-task and nothing else. Beside granted, the host would commit a break nobody asked for.
1523
- into.require(value.approval === "in-effect" || value.approval === "starting-after-task", "break.imposed.approval", `${path}.imposed`, `an imposed break is in effect or starting after the task; ${describeValue(value.approval)} says the agent asked, or that there is none`);
1524
+ into.require(value.approval === "in-effect" || value.approval === "starting-after-task", "break.forced.approval", `${path}.forced`, `a forced break is in effect or starting after the task; ${describeValue(value.approval)} says the agent asked, or that there is none`);
1524
1525
  }
1525
1526
  if (value.reasons === undefined)
1526
1527
  return;
@@ -1556,7 +1557,7 @@ function validateBreakState(value, path, into) {
1556
1557
  into.require(seen.has(value.activeReasonId), "break.activeReasonId.known", `${path}.activeReasonId`, `activeReasonId names a reason the provider did not publish: ${value.activeReasonId}`);
1557
1558
  }
1558
1559
  // A break in effect, or about to be, on a provider that publishes reasons is on one of them: an
1559
- // imposed one included, since the lead's place named it. A break of no kind is a break whose
1560
+ // forced one included, since the lead's place named it. A break of no kind is a break whose
1560
1561
  // rules -- who may listen through it, whether it counts -- nobody can apply.
1561
1562
  ruleEvaluated("break.activeReasonId.required");
1562
1563
  if (seen.size > 0 && (value.approval === "in-effect" || value.approval === "starting-after-task")) {
package/guide.md CHANGED
@@ -949,7 +949,7 @@ type BreakRequest = {
949
949
  reasonId?: string;
950
950
  };
951
951
 
952
- type ImposedBreak =
952
+ type ForcedBreak =
953
953
  | { by: UserId; endsAutomatically: true; endsAt: IsoTimestamp }
954
954
  | { by: UserId; endsAutomatically: false; endsAt?: never };
955
955
 
@@ -961,7 +961,7 @@ type BreakState = {
961
961
  retryAfterMs?: number;
962
962
  reasons?: BreakReason[];
963
963
  activeReasonId?: string;
964
- imposed?: ImposedBreak;
964
+ forced?: ForcedBreak;
965
965
  };
966
966
 
967
967
  type BreakRequestResult =
@@ -2023,7 +2023,7 @@ failure:
2023
2023
  ```
2024
2024
 
2025
2025
  The `User` it carries is the **root of this provider's user namespace**. Every other person this
2026
- provider names — a team member, the manager on an imposed break, the agent on an interaction step —
2026
+ provider names — a team member, the manager on a forced break, the agent on an interaction step —
2027
2027
  is identified from the same directory and carries the same `UserId` type.
2028
2028
 
2029
2029
  | Field | Contract |
@@ -2131,7 +2131,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
2131
2131
  | --- | --- |
2132
2132
  | `transport` | Current `TransportStatus` — whether this provider's transport can serve the login. Defined under **`transport-status`**. |
2133
2133
  | `loginId` | Identity of this login. It must match the connection context. |
2134
- | `break` | Complete break state, including approval, whether the agent may ask, reasons, retry details, and any imposed break. |
2134
+ | `break` | Complete break state, including approval, whether the agent may ask, reasons, retry details, and any forced break. |
2135
2135
  | `tasks` | Complete set of tasks currently offered to or owned by this agent. |
2136
2136
  | `taskCount` | The provider's own count of those tasks, stated rather than inferred, and it must equal `tasks.length`. A snapshot with no work says `taskCount: 0` in so many words — a blank or unanswered state lacks the count and cannot pass as a confirmed empty. |
2137
2137
  | `contacts` | Required complete contact contribution when the manifest declares `contacts`; `[]` clears it. Omitted only when it does not. |
@@ -2153,7 +2153,7 @@ surface in one place, and what obliges an adapter to implement each one.
2153
2153
  | `refused(report)` | Always. The host tells the adapter what it would not take -- a snapshot it did not replace its state with, an event it dropped -- with every rule broken, so a refusal is visible on both sides. See **What the host does with what it refuses**. |
2154
2154
  | `setCapacity(capacity)` | Always. Nothing may be allocated until a capacity is stated, so there is no connection that does not receive it. |
2155
2155
  | `execute(request)` | Always. Every channel has commands no capability gates — see **Which commands need a capability**. |
2156
- | `describeUsers(ids)` | The adapter publishes any `UserId`: on `ImposedBreak.by`, a team member list, or `interactionHistory[].by`. Each `User` carries its `timeZone`; a person whose zone the provider cannot name is omitted from the answer, as any unresolvable id is. |
2156
+ | `describeUsers(ids)` | The adapter publishes any `UserId`: on `ForcedBreak.by`, a team member list, or `interactionHistory[].by`. Each `User` carries its `timeZone`; a person whose zone the provider cannot name is omitted from the answer, as any unresolvable id is. |
2157
2157
  | `dial(request)` | The manifest declares `idleCapabilities.dial`, and with it `dialOutcomes`. |
2158
2158
  | `requestBreak(request)` | The login declares `capabilities.breaks`. |
2159
2159
  | `commitBreak()` | The login declares `capabilities.breaks`. Commit and cancel are not optional halves of it. |
@@ -2211,7 +2211,7 @@ Turns `UserId` values into something an agent can read.
2211
2211
  describeUsers(ids: UserId[]): Promise<User[]>
2212
2212
  ```
2213
2213
 
2214
- Required of any adapter that publishes a `UserId` — on `ImposedBreak.by`, a team member list, or
2214
+ Required of any adapter that publishes a `UserId` — on `ForcedBreak.by`, a team member list, or
2215
2215
  `interactionHistory[].by`. Publishing an identifier Omni cannot resolve puts a name on screen
2216
2216
  that reads as a database key.
2217
2217
 
@@ -2856,7 +2856,7 @@ Four rules a provider has to keep:
2856
2856
  conversation, and on live data that is the ordinary case rather than an edge. A zero is rejected.
2857
2857
  - **`by` is a bare `UserId`, and not necessarily an agent.** A lead or a manager takes part
2858
2858
  during an interaction too — a transfer accepted, a call conferenced in — so the field names whoever it was,
2859
- the same way `ImposedBreak.by` does. It comes from this provider's own directory, the same
2859
+ the same way `ForcedBreak.by` does. It comes from this provider's own directory, the same
2860
2860
  namespace as `AuthenticationState.identity.id` and the team member list, so entries pair
2861
2861
  within a provider and never across one.
2862
2862
  - **A task carries no names.** Omni resolves what to display with `describeUsers()`. Two people
@@ -3613,8 +3613,8 @@ nothing while none are being accepted — so they are not published separately.
3613
3613
  | `decisionReason` | The words whoever decided attached, from `decide.reason`. About one request and one decision, not a standing gate. |
3614
3614
  | `retryAfterMs` | How long until the agent may retry, when the provider can say. |
3615
3615
  | `reasons` | Not-ready codes this provider offers. Omitted when it defines none; an empty list is refused, being a second spelling of the same fact. |
3616
- | `activeReasonId` | The `BreakReason.id` the current break is on. Omitted when there is no break. Required on a break `in-effect` or `starting-after-task` where the provider publishes `reasons`, an imposed break included (`break.activeReasonId.required`): a break with a kind the provider cannot name is a break whose rules nobody can apply. |
3617
- | `imposed` | Set when the break was placed on the agent rather than requested. |
3616
+ | `activeReasonId` | The `BreakReason.id` the current break is on. Omitted when there is no break. Required on a break `in-effect` or `starting-after-task` where the provider publishes `reasons`, a forced break included (`break.activeReasonId.required`): a break with a kind the provider cannot name is a break whose rules nobody can apply. |
3617
+ | `forced` | Set when the break was placed on the agent rather than requested. |
3618
3618
 
3619
3619
  A request can be waiting for two unrelated things, and they are separate values because
3620
3620
  rendering one as the other tells an agent to wait for somebody who is never coming:
@@ -3640,42 +3640,48 @@ it; it sends the commit again only from a reconnect snapshot that shows the gran
3640
3640
  refused. A `BreakReason` marked `alwaysAvailable` survives it: a mandatory rest period is not
3641
3641
  something a busy hour can cancel, and Omni keeps offering those while the rest are withdrawn.
3642
3642
 
3643
- ### Imposed breaks
3643
+ ### Forced breaks
3644
3644
 
3645
- `ImposedBreak` says who placed the break, whether automatic ending is enabled, and, when enabled,
3645
+ Migration: ImposedBreak is now `ForcedBreak`, and the former imposed field is now
3646
+ `BreakState.forced`. Diagnostic and harness coverage names use `break.forced`; the agent-end
3647
+ diagnostic is `break.command.end.forced`. Update hosts and providers together. The old field
3648
+ is rejected, including when both spellings are sent; no compatibility alias is provided.
3649
+ The `place` and `release` commands and break ordering rules are unchanged.
3650
+
3651
+ `ForcedBreak` says who placed the break, whether automatic ending is enabled, and, when enabled,
3646
3652
  when the provider will end it. A break the agent did not choose is not manually resumable by them.
3647
3653
 
3648
- **Every imposed break has a person behind it.** A lead or a manager placed it; there is no such
3649
- thing as a break the platform imposed on its own. Where a platform applies one automatically, it is
3654
+ **Every forced break has a person behind it.** A lead or a manager placed it; there is no such
3655
+ thing as a break the platform forced on its own. Where a platform applies one automatically, it is
3650
3656
  executing a preference somebody configured, and that person is the owner of the action — `by` names
3651
3657
  them, not the machinery that carried it out.
3652
3658
 
3653
3659
  Omni resolves the name to show with `describeUsers()`, so a provider sends the identifier and never
3654
3660
  a display name.
3655
3661
 
3656
- **An imposed break travels with `in-effect` or `starting-after-task`, and nothing else.** It is a
3662
+ **A forced break travels with `in-effect` or `starting-after-task`, and nothing else.** It is a
3657
3663
  break in progress or about to be; beside `granted` or `awaiting-decision` the host would read a
3658
- request the agent never made and commit it (`break.imposed.approval`). Where the provider publishes
3664
+ request the agent never made and commit it (`break.forced.approval`). Where the provider publishes
3659
3665
  `reasons`, the lead's `place` named one, and the member's state carries it as `activeReasonId`.
3660
3666
 
3661
3667
  For example:
3662
3668
 
3663
3669
  ```ts
3664
- imposed: {
3670
+ forced: {
3665
3671
  by: "manager-1042",
3666
3672
  endsAutomatically: true,
3667
3673
  endsAt: "2026-08-21T10:00:00.000Z"
3668
3674
  }
3669
3675
  ```
3670
3676
 
3671
- The presence of `imposed` means the agent cannot end the break manually, so Omni withdraws its
3677
+ The presence of `forced` means the agent cannot end the break manually, so Omni withdraws its
3672
3678
  Resume control from that agent. With `endsAutomatically: true`, the provider ends the break at
3673
3679
  `endsAt`; with `endsAutomatically: false`, it does not end the break on a timer. An authorized lead
3674
3680
  may end either form with **Resume**, not only whoever placed it. Omni shows **Stopped by <who>**,
3675
3681
  resolving the name with `describeUsers()`, and shows when the break will end where automatic ending
3676
3682
  is enabled.
3677
3683
 
3678
- A break applies to the **agent**, not to one provider. When a provider imposes one, Omni immediately
3684
+ A break applies to the **agent**, not to one provider. When a provider forces one, Omni immediately
3679
3685
  requests a break on every other connected provider, or they would keep routing work to somebody who
3680
3686
  is not there. Providers should expect that follow-on request.
3681
3687
 
@@ -3760,13 +3766,13 @@ request object only to the request method, and undefined to the other three.
3760
3766
  | `requestBreak` | `not-requested`; selected current reason code when codes exist; `mayAsk` or the selected reason's `alwaysAvailable` exception. Free text does not replace a code. |
3761
3767
  | `commitBreak` | `granted`, or already committed for an idempotent repeat. A later change to `mayAsk` does not revoke the grant. |
3762
3768
  | `cancelBreak` | `awaiting-decision` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
3763
- | `endBreak` | `in-effect` or `starting-after-task` during reconciliation; an agent cannot end an imposed break. |
3769
+ | `endBreak` | `in-effect` or `starting-after-task` during reconciliation; an agent cannot end a forced break. |
3764
3770
 
3765
3771
  Use `validateTeamBreakCommand(request, context)` for lead decisions, placement, release and
3766
3772
  policy commands. It requires the live lead capability and active transport, a current target
3767
3773
  team member list for member commands, and the target's complete break state for placement/release.
3768
3774
  Approve/deny requires an awaiting decision; placement uses the target's reason codes; release
3769
- requires an imposed committed break. The context's target state must belong to the named member;
3775
+ requires a forced committed break. The context's target state must belong to the named member;
3770
3776
  that association and backend authorization are provider responsibilities.
3771
3777
 
3772
3778
  Use `validateBreakStatus(state, tasks)` on the complete retained task view after each transaction,
@@ -3855,7 +3861,7 @@ specificity: about this agent's own work is `coaching`, else something they must
3855
3861
  `training`, else `meeting`.
3856
3862
 
3857
3863
  **A break somebody placed on the agent is not automatically one of these.** Where the agent was
3858
- stopped rather than choosing to stop — see **Imposed breaks** — set `BreakState.imposed` and prefer
3864
+ stopped rather than choosing to stop — see **Forced breaks** — set `BreakState.forced` and prefer
3859
3865
  omitting `kind` to reaching for `other`. None of the ten describes "something was done to this
3860
3866
  agent", and `other` claims a classification that was never made.
3861
3867
 
@@ -3984,8 +3990,8 @@ If cancel races with a late approval, the provider remains on the cancel path. I
3984
3990
  won, cancel returns `omni.break-already-committed`, and Omni resumes commit recovery rather than
3985
3991
  returning the agent to `working`.
3986
3992
 
3987
- An imposed break is not rolled back by this algorithm. If one appears during either the request or
3988
- cancellation, Omni follows the imposed-break rule on the other providers and commits each grant as
3993
+ A forced break is not rolled back by this algorithm. If one appears during either the request or
3994
+ cancellation, Omni follows the forced-break rule on the other providers and commits each grant as
3989
3995
  soon as it reaches `granted`; it does not wait for unanimity because the agent has already
3990
3996
  stopped elsewhere.
3991
3997
 
@@ -4094,8 +4100,8 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
4094
4100
  | --- | --- |
4095
4101
  | `{ type: "decide", memberId: UserId, decision, reason? }` | Settles one pending request. `decision` is `granted` or `denied`. A grant moves the member to `granted`; a denial ends the request and moves it directly to `not-requested`. |
4096
4102
  | `{ type: "policy", policy }` | `ask`, `auto-approve`, or `suspended`. |
4097
- | `{ type: "place", memberId: UserId, reasonId?, reason? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's imposed break carries it as `activeReasonId`, so its kind is known. |
4098
- | `{ type: "release", memberId: UserId }` | Ends an imposed break on that member, whoever placed it. |
4103
+ | `{ type: "place", memberId: UserId, reasonId?, reason? }` | Puts a member on a break they did not ask for. `reasonId` names a published `BreakReason.id` and is required whenever the provider publishes `reasons`; the member's forced break carries it as `activeReasonId`, so its kind is known. |
4104
+ | `{ type: "release", memberId: UserId }` | Ends a forced break on that member, whoever placed it. |
4099
4105
 
4100
4106
  `memberId` is this provider's own identifier for the member, as published on its team member list. It is
4101
4107
  never an identifier from another provider, and Omni does not translate between them; names come
@@ -4103,7 +4109,7 @@ from `describeUsers()`.
4103
4109
 
4104
4110
  `suspended` means requests are **rejected outright** rather than left pending — nobody is coming to
4105
4111
  approve them. A provider that suspends breaks must also publish `mayAsk: false` to the team's
4106
- agents so they see it before asking. A `place` must likewise reach that member as an `imposed` break
4112
+ agents so they see it before asking. A `place` must likewise reach that member as an `forced` break
4107
4113
  on their own `BreakState`, or they are stopped from working with no way to see why.
4108
4114
 
4109
4115
  What happens when no lead is online — auto-approving, for instance — is the provider's decision and is
@@ -4938,11 +4944,11 @@ covers a `diagnostic`: shown where the agent works, and counted.
4938
4944
  Replaces this provider's complete `break` object. Its `approval` uses the canonical
4939
4945
  `not-requested`, `awaiting-decision`, `granted`, `starting-after-task` and
4940
4946
  `in-effect` states defined under Breaks; the event also carries the corresponding may-ask state,
4941
- reasons, retry details, and any imposed break.
4947
+ reasons, retry details, and any forced break.
4942
4948
 
4943
4949
  Each state is also held to the one before it. A commit's states, `starting-after-task` and
4944
4950
  `in-effect`, follow a grant — the one arrival in a committed state nobody asked for is a placed
4945
- break, which says so with `imposed`: in effect at once, or `starting-after-task` while the member
4951
+ break, which says so with `forced`: in effect at once, or `starting-after-task` while the member
4946
4952
  finishes the call they are on (`stream.breakState.commitBeforeGrant`); and a break never moves backwards —
4947
4953
  from `in-effect` or `starting-after-task` to a grant or a request, or from `granted` to
4948
4954
  `awaiting-decision` — a new request passes through `not-requested` (`stream.breakState.backwards`).
@@ -4975,8 +4981,8 @@ Normal order is `not-requested` → `awaiting-decision` → `granted` →
4975
4981
  `starting-after-task` → `in-effect` → `not-requested`. Auto-approval may go directly to
4976
4982
  `granted`; a commit with no outstanding work may go directly to `in-effect`. Denial/cancel
4977
4983
  returns a precommit request to `not-requested`; an authorized end/release returns a committed
4978
- break there. Same-state restatements are allowed. An evidenced imposed break is the explicit
4979
- exception to requesting/granting, and must carry its imposed actor/state. Neither skipped
4984
+ break there. Same-state restatements are allowed. An evidenced forced break is the explicit
4985
+ exception to requesting/granting, and must carry its forced actor/state. Neither skipped
4980
4986
  publication nor a host-local guess creates another exception. A later normal attempt starts
4981
4987
  from `not-requested`, never by regressing an active break into a request.
4982
4988
 
@@ -5145,7 +5151,7 @@ issued by each provider independently, so two providers will eventually issue th
5145
5151
  different people. Encode and join before storing or comparing.
5146
5152
 
5147
5153
  Use it for every `UserId` — `interactionHistory[].by`, team members, `memberId` on a
5148
- lead command, `ImposedBreak.by`. A bare one is only ever compared against another from the **same** provider; anything
5154
+ lead command, `ForcedBreak.by`. A bare one is only ever compared against another from the **same** provider; anything
5149
5155
  wider goes through this key.
5150
5156
 
5151
5157
  ### `sameCapabilities(a, b)`
@@ -5256,7 +5262,7 @@ A capability granted by a later login requires its methods just as one declared
5256
5262
 
5257
5263
  `result.notExercised` lists what the run never reached — one subject per family of rules: each
5258
5264
  optional part of a task (`task.browsers`, `task.interactionHistory`, `task.leadAssist`, …), the break's
5259
- `reasons` and `imposed`, the team member list's `members` and `requests`, each declared contribution, and
5265
+ `reasons` and `forced`, the team member list's `members` and `requests`, each declared contribution, and
5260
5266
  each event type (`event.task-ended`, …) — and so what a clean `violations` says nothing about.
5261
5267
  Nothing there is a violation: an adapter with no team has nothing to exercise. But a fixture with
5262
5268
  no tasks exercises no task rule, and a pass over it reads as coverage it is not.
@@ -5374,7 +5380,7 @@ cannot be established from TypeScript structure alone.
5374
5380
  | `assertReconnectWithMissedAssignments(before, reconnect, ids)` | A reconnect snapshot restores assignments received while offline. |
5375
5381
  | `stillHost(report?, guarantees?, mute?)` | A host that reports one thing and never changes, for a test context: `{ online: true }` by default, a report with audio for a softphone voice adapter, and never for a desk phone. |
5376
5382
  | `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for a host that wants the same rules at its boundary: `seed(snapshot)`, then `apply(envelope)` returns the violations. |
5377
- | `assertBreakFollowsItsRequests(envelopes, snapshot?)` | A break follows its requests: a commit's states only after a grant, never backwards, and a placed break arriving in effect with `imposed`. The harness applies the same rules after the connect snapshot. |
5383
+ | `assertBreakFollowsItsRequests(envelopes, snapshot?)` | A break follows its requests: a commit's states only after a grant, never backwards, and a placed break arriving in effect with `forced`. The harness applies the same rules after the connect snapshot. |
5378
5384
  | `assertMediaFollowsTheTask(envelopes, snapshot?)` | The media follows the task and never decides it: every task is introduced once, `task-media-started` and `task-media-ended` alternate on work that has begun, media ends only where it arrived, and what follows the media ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no media satisfies it by never testing it — pair it with the assertion that the media end is present. |
5379
5385
  | `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
5380
5386
  | `assertBreakBeginsAfterTask(steps)` | A break asked for on a task is committed as `starting-after-task` while work remains and reaches `in-effect` only once nothing is outstanding — never beside a task, never later than the step that has none. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.90",
3
+ "version": "0.1.91",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",