@xema/omni-protocol 0.1.84 → 0.1.86

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/README.md CHANGED
@@ -42,9 +42,9 @@ assertNoViolations(violations);
42
42
  A violation carries a stable `rule` id such as `task.browser.url.scheme`, the `path` it was found
43
43
  at such as `snapshot.tasks[0].browsers[1].url`, and a `message`.
44
44
 
45
- Some rules need more than the object in hand. A roster never carries the agent it is published to,
45
+ Some rules need more than the object in hand. A team member list never carries the agent it is published to,
46
46
  a lead's snapshot always carries one and nobody else's ever does — and a validator cannot know who
47
- is reading, or what their login declares, from the snapshot alone. `validateTeamRoster`,
47
+ is reading, or what their login declares, from the snapshot alone. `validateTeamMembers`,
48
48
  `validateSnapshot`, and `validateEventEnvelope` each take an optional final `{ self, capabilities }`
49
49
  from the `authenticated` state; given them, they report `team.member.self`, `team.request.self`,
50
50
  `team.required`, `team.unentitled`, `team.requests.capability`, and `team.requests.required`.
@@ -61,7 +61,7 @@ validateSnapshot(snapshot, manifest, "snapshot", { self: identity.id, capabiliti
61
61
  required capability methods, subscribes, validates the snapshot, every delivered event, and every
62
62
  authentication state published during the run — against the latest login — states a capacity,
63
63
  then unsubscribes and disconnects. `result.notExercised` names what the run never reached — each
64
- optional part of a task, of the break state and roster, each contribution, each event type — so a
64
+ optional part of a task, of the break state and team member list, each contribution, each event type — so a
65
65
  clean result is read for what it covers and not for the whole contract; `assertReached(result,
66
66
  subjects)` is the paired assertion.
67
67
 
package/dist/index.d.ts CHANGED
@@ -268,7 +268,7 @@ export interface UserCapabilities {
268
268
  breaks?: true;
269
269
  /** The choices the team left to this person, with where each stands. Omitted when there are none. Requires `setPreference`. */
270
270
  preferences?: AgentPreference[];
271
- /** This login leads a team: a `TeamRoster` is published to it on every snapshot, `[]` included. */
271
+ /** This login leads a team: a `TeamMembers` object is published to it on every snapshot, including `members: []`. */
272
272
  team?: TeamCapabilities;
273
273
  }
274
274
  /**
@@ -619,15 +619,15 @@ export interface ScheduledActivity {
619
619
  party?: Contact;
620
620
  attributes?: Attribute[];
621
621
  }
622
- export interface DispositionCode {
622
+ export interface OutcomeCode {
623
623
  id: string;
624
624
  label: string;
625
625
  group?: string;
626
626
  }
627
- export interface DispositionRules {
627
+ export interface OutcomeRules {
628
628
  required?: boolean;
629
629
  notes?: "required" | "optional" | "none";
630
- codes?: DispositionCode[];
630
+ codes?: OutcomeCode[];
631
631
  }
632
632
  /**
633
633
  * One item the queue configured for the agent to send a contact to: a button or a menu item. The
@@ -658,7 +658,7 @@ export interface CustomCapability {
658
658
  }
659
659
  export interface SharedTaskCapabilities {
660
660
  browsers?: true;
661
- dispositions?: true | DispositionRules;
661
+ outcomes?: true | OutcomeRules;
662
662
  custom?: CustomCapability[];
663
663
  }
664
664
  /**
@@ -1054,8 +1054,8 @@ export declare const TASK_COMMAND_NAMES: {
1054
1054
  readonly email: readonly ["accept", "decline", "complete"];
1055
1055
  };
1056
1056
  export type TaskCommandName<C extends keyof typeof TASK_COMMAND_NAMES> = (typeof TASK_COMMAND_NAMES)[C][number];
1057
- export interface DispositionPayload {
1058
- disposition?: string;
1057
+ export interface OutcomePayload {
1058
+ outcome?: string;
1059
1059
  notes?: string;
1060
1060
  }
1061
1061
  export type VoiceTaskCommand = {
@@ -1153,7 +1153,7 @@ export type VoiceTaskCommand = {
1153
1153
  source: "provider";
1154
1154
  }) | ({
1155
1155
  type: "complete";
1156
- } & DispositionPayload);
1156
+ } & OutcomePayload);
1157
1157
  export type ChatTaskCommand = {
1158
1158
  type: "accept";
1159
1159
  } | {
@@ -1164,14 +1164,14 @@ export type ChatTaskCommand = {
1164
1164
  type: "resume";
1165
1165
  } | ({
1166
1166
  type: "complete";
1167
- } & DispositionPayload);
1167
+ } & OutcomePayload);
1168
1168
  export type EmailTaskCommand = {
1169
1169
  type: "accept";
1170
1170
  } | {
1171
1171
  type: "decline";
1172
1172
  } | ({
1173
1173
  type: "complete";
1174
- } & DispositionPayload);
1174
+ } & OutcomePayload);
1175
1175
  export interface CustomTaskCommand {
1176
1176
  type: "custom";
1177
1177
  name: string;
@@ -1336,7 +1336,7 @@ export interface LeadRequest {
1336
1336
  * The login is the permission: published to a login that declares `capabilities.team`, on every
1337
1337
  * snapshot, and to nobody else. What the lead may do with it is on the login too, not here.
1338
1338
  */
1339
- export interface TeamRoster {
1339
+ export interface TeamMembers {
1340
1340
  members: TeamMember[];
1341
1341
  /** Omitted when the login lacks `team.leadAssistControl`; `[]` when nobody is asking. */
1342
1342
  requests?: LeadRequest[];
@@ -1372,7 +1372,7 @@ export interface TeamLeadAssistCommandRequest {
1372
1372
  command: TeamLeadAssistCommand;
1373
1373
  }
1374
1374
  /**
1375
- * A lead listening to a member's call. `monitor` starts one, silent, on a member from the roster;
1375
+ * A lead listening to a member's call. `monitor` starts one, silent, on a member from the team member list;
1376
1376
  * `whisper` and `barge` change how the standing one is heard; `leave` ends it. One member at a
1377
1377
  * time, and only the modes the login's `monitorControl` lists.
1378
1378
  */
@@ -1530,7 +1530,7 @@ export interface Snapshot<C extends Channel = Channel> {
1530
1530
  taskCount: number;
1531
1531
  contacts?: Contact[];
1532
1532
  scheduledActivities?: ScheduledActivity[];
1533
- team?: TeamRoster;
1533
+ team?: TeamMembers;
1534
1534
  }
1535
1535
  /**
1536
1536
  * How many tasks this provider may have allocated to the agent at once.
@@ -1627,7 +1627,7 @@ export type ProviderEvent<C extends Channel = Channel> = {
1627
1627
  allocationId?: AllocationId;
1628
1628
  } | {
1629
1629
  type: "team-updated";
1630
- team: TeamRoster;
1630
+ team: TeamMembers;
1631
1631
  } | {
1632
1632
  type: "contacts-updated";
1633
1633
  contacts: Contact[];
@@ -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 roster, or history. */
1695
+ /** Required of any adapter publishing a `UserId` -- on an imposed 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
@@ -4,10 +4,10 @@ export { ProtocolConformanceError, assertNoViolations, type ProtocolViolation }
4
4
  /**
5
5
  * A part of the contract a run may never reach: state nothing obliges an adapter to publish, so
6
6
  * a fixture without it exercises none of its rules and passes clean. One subject per family of
7
- * rules -- each optional part of a task, each optional part of the break state and roster, each
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.monitoring", "task.media", "task.acceptance", "task.dispositions", "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.monitoring", "task.media", "task.acceptance", "task.outcomes", "task.destinations", "task.custom", "task.locked", "break.reasons", "break.imposed", "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[];
@@ -89,7 +89,7 @@ export declare function assertAuthenticationRestoreAndExpiry(states: readonly Au
89
89
  * session published, first to last: beginning and ending `authenticated` for the same identity,
90
90
  * passing only through usable states (`expired` or `signed-out` ends the login instead), with at
91
91
  * least one capability gone by the end. `snapshot` is the first snapshot published after the last
92
- * state, validated against that login -- so a roster still published to a login that no longer
92
+ * state, validated against that login -- so a team member list still published to a login that no longer
93
93
  * leads, or requests to one that may no longer join, is the failure.
94
94
  */
95
95
  export declare function assertCapabilityWithdrawal(states: readonly AuthenticationState[], snapshot: Snapshot, manifest: Manifest): void;
@@ -121,7 +121,7 @@ export declare function assertCommandRefusedAfterWithdrawal(result: {
121
121
  export declare function assertTaskCapabilityWithdrawal(tasks: readonly Task[], manifest: Manifest, command: Record<string, unknown>): void;
122
122
  /**
123
123
  * Throws unless the run reached every subject named: the paired assertion beside a clean result,
124
- * so a fixture that never produced a roster cannot pass a test that meant to check one.
124
+ * so a fixture that never produced a team member list cannot pass a test that meant to check one.
125
125
  */
126
126
  export declare function assertReached(result: AdapterContractResult, subjects: readonly ContractSubject[]): void;
127
127
  /** Validates duplicate delivery and returns the event sequence Omni applies once per ID. */
package/dist/testing.js CHANGED
@@ -4,7 +4,7 @@ export { ProtocolConformanceError, assertNoViolations } from "./validation.js";
4
4
  /**
5
5
  * A part of the contract a run may never reach: state nothing obliges an adapter to publish, so
6
6
  * a fixture without it exercises none of its rules and passes clean. One subject per family of
7
- * rules -- each optional part of a task, each optional part of the break state and roster, each
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
10
  const STATE_SUBJECTS = [
@@ -18,7 +18,7 @@ const STATE_SUBJECTS = [
18
18
  "task.monitoring",
19
19
  "task.media",
20
20
  "task.acceptance",
21
- "task.dispositions",
21
+ "task.outcomes",
22
22
  "task.destinations",
23
23
  "task.custom",
24
24
  "task.locked",
@@ -68,8 +68,8 @@ function observeTask(value, seen) {
68
68
  if (value.acceptance !== undefined)
69
69
  seen.add("task.acceptance");
70
70
  const capabilities = isRecord(value.capabilities) ? value.capabilities : {};
71
- if (isRecord(capabilities.dispositions))
72
- seen.add("task.dispositions");
71
+ if (isRecord(capabilities.outcomes))
72
+ seen.add("task.outcomes");
73
73
  for (const directory of ["coldTransfer", "warmTransfer", "conference"]) {
74
74
  const declared = capabilities[directory];
75
75
  if (isRecord(declared) && some(declared.destinations))
@@ -188,7 +188,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
188
188
  throw new Error(`Adapter contract exercise requires authenticated test state, received ${authenticationState.status}`);
189
189
  }
190
190
  // The harness knows who is signed in and what their login declares, so it can hold the
191
- // adapter to rules the structural validators cannot check alone: a roster never carries the
191
+ // adapter to rules the structural validators cannot check alone: a team member list never carries the
192
192
  // agent it is published to, a lead's snapshot always carries one, nobody else's ever does.
193
193
  // Capabilities are current, not fixed, so the login is read when something is validated,
194
194
  // never captured at sign-in: a provider that withdraws one republishes `authenticated`, and
@@ -460,7 +460,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
460
460
  if (publishesUserIds(read)) {
461
461
  requireMethod(source, "describeUsers", "the snapshot publishes a UserId");
462
462
  // Required by presence is not enough: the names the snapshot published are looked up, and what
463
- // comes back is held to the shape -- an empty answer to a roster of colleagues is named.
463
+ // comes back is held to the shape -- an empty answer to a team member list of colleagues is named.
464
464
  const named = userIdsIn(read);
465
465
  if (typeof source.describeUsers === "function" && named.length > 0) {
466
466
  let described;
@@ -644,7 +644,7 @@ 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 roster. */
647
+ /** Every UserId a snapshot publishes: on the record, the room, a lead request, an imposed 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)
@@ -685,7 +685,7 @@ const taskNamesUsers = (task) => isRecord(task) && ((isRecord(task.interactionHi
685
685
  (isRecord(task.leadAssist) && task.leadAssist.leadId !== undefined) ||
686
686
  isRecord(task.assisting) ||
687
687
  isRecord(task.monitoring));
688
- /** Whether an event publishes a `UserId`, on a roster, a task, or the snapshot a reconnect carries. */
688
+ /** Whether an event publishes a `UserId`, on a team member list, a task, or the snapshot a reconnect carries. */
689
689
  function eventNamesUsers(envelope) {
690
690
  const event = isRecord(envelope) ? envelope.event : undefined;
691
691
  if (!isRecord(event))
@@ -795,7 +795,7 @@ function undeterminedTasks(tasks, path) {
795
795
  * session published, first to last: beginning and ending `authenticated` for the same identity,
796
796
  * passing only through usable states (`expired` or `signed-out` ends the login instead), with at
797
797
  * least one capability gone by the end. `snapshot` is the first snapshot published after the last
798
- * state, validated against that login -- so a roster still published to a login that no longer
798
+ * state, validated against that login -- so a team member list still published to a login that no longer
799
799
  * leads, or requests to one that may no longer join, is the failure.
800
800
  */
801
801
  export function assertCapabilityWithdrawal(states, snapshot, manifest) {
@@ -890,7 +890,7 @@ export function assertTaskCapabilityWithdrawal(tasks, manifest, command) {
890
890
  }
891
891
  /**
892
892
  * Throws unless the run reached every subject named: the paired assertion beside a clean result,
893
- * so a fixture that never produced a roster cannot pass a test that meant to check one.
893
+ * so a fixture that never produced a team member list cannot pass a test that meant to check one.
894
894
  */
895
895
  export function assertReached(result, subjects) {
896
896
  const missed = subjects.filter(subject => result.notExercised.includes(subject));
@@ -1793,9 +1793,9 @@ async function driveOneCall(drive) {
1793
1793
  const completable = latestTask().phase === "completing" || latestTask().phase === "in-progress" || latestTask().phase === "paused";
1794
1794
  if (latestTask().completionMode === "agent-command" && completable) {
1795
1795
  const command = { type: "complete" };
1796
- const dispositions = isRecord(latestTask().capabilities) ? latestTask().capabilities.dispositions : undefined;
1797
- if (isRecord(dispositions) && dispositions.required === true && Array.isArray(dispositions.codes) && isRecord(dispositions.codes[0])) {
1798
- command.disposition = dispositions.codes[0].id;
1796
+ const outcomes = isRecord(latestTask().capabilities) ? latestTask().capabilities.outcomes : undefined;
1797
+ if (isRecord(outcomes) && outcomes.required === true && Array.isArray(outcomes.codes) && isRecord(outcomes.codes[0])) {
1798
+ command.outcome = outcomes.codes[0].id;
1799
1799
  }
1800
1800
  if (await send(command) !== undefined) {
1801
1801
  // applied says the provider has completed the task, and its ending follows within the bound
@@ -50,9 +50,9 @@ export declare function validateTask(task: unknown, context: TaskValidationConte
50
50
  /** Checks break status against the full currently retained task set, after a transaction. */
51
51
  export declare function validateBreakStatus(state: unknown, tasks: unknown, path?: string): ProtocolViolation[];
52
52
  /**
53
- * Checks lead break dispatch using current authentication, transport, roster (`team`) and,
53
+ * Checks lead break dispatch using current authentication, transport, team member list (`team`) and,
54
54
  * for place/release, the target's full `memberBreak`. The provider must authorize the target
55
- * and recheck the decision atomically; a roster is not authority to act after it has changed.
55
+ * and recheck the decision atomically; a team member list is not authority to act after it has changed.
56
56
  */
57
57
  export declare function validateTeamBreakCommand(request: unknown, context: unknown, path?: string): ProtocolViolation[];
58
58
  /** The four agent break methods; this is a validation API, not a wire command. */
@@ -78,12 +78,12 @@ export declare function validateBreakTransition(before: unknown, after: unknown,
78
78
  export interface ReaderContext {
79
79
  /**
80
80
  * The signed-in agent, `AuthenticationState.identity.id`. A lead does not report to themself:
81
- * a roster that lists them in `members`, or their own ask in `requests`, is a violation.
81
+ * a team member list that lists them in `members`, or their own ask in `requests`, is a violation.
82
82
  */
83
83
  self?: UserId;
84
84
  /**
85
85
  * The login's `AuthenticationState.capabilities`. The login is the permission: a lead's
86
- * snapshot carries a roster, nobody else's does, and `requests` need `team.leadAssistControl`.
86
+ * snapshot carries a team member list, nobody else's does, and `requests` need `team.leadAssistControl`.
87
87
  */
88
88
  capabilities?: UserCapabilities;
89
89
  /** The level ids in force. Filled from the manifest by `validateSnapshot` and `validateEventEnvelope`; the defaults otherwise. */
@@ -99,7 +99,7 @@ export interface ReaderContext {
99
99
  /** `ConnectContext.autoAcceptTasks` as sent: whether a pending task states its `acceptance`. Unknown to a caller without the context, and then unchecked. */
100
100
  autoAcceptTasks?: boolean;
101
101
  }
102
- export declare function validateTeamRoster(roster: unknown, path?: string, context?: ReaderContext): ProtocolViolation[];
102
+ export declare function validateTeamMembers(teamMembers: unknown, path?: string, context?: ReaderContext): ProtocolViolation[];
103
103
  export declare function validateSnapshot(snapshot: unknown, manifest: unknown, path?: string, context?: ReaderContext): ProtocolViolation[];
104
104
  export declare function validateEventEnvelope(envelope: unknown, manifest: unknown, path?: string, context?: ReaderContext): ProtocolViolation[];
105
105
  /**
@@ -98,11 +98,11 @@ const ISOLATION_SCHEME_VALUES = Object.values(BROWSER_ISOLATION_SCHEMES);
98
98
  /** The capabilities each channel arm of `TaskCapabilities` declares, keyed off the type itself. */
99
99
  const TASK_CAPABILITIES = {
100
100
  voice: membersOf({
101
- browsers: true, dispositions: true, custom: true, decline: true, hold: true,
101
+ browsers: true, outcomes: true, custom: true, decline: true, hold: true,
102
102
  endCall: true, connectBack: true, coldTransfer: true, warmTransfer: true, leadAssist: true, conference: true, recording: true,
103
103
  }),
104
- chat: membersOf({ browsers: true, dispositions: true, custom: true, decline: true, hold: true }),
105
- email: membersOf({ browsers: true, dispositions: true, custom: true, decline: true }),
104
+ chat: membersOf({ browsers: true, outcomes: true, custom: true, decline: true, hold: true }),
105
+ email: membersOf({ browsers: true, outcomes: true, custom: true, decline: true }),
106
106
  };
107
107
  // The published list and the type's keys are the same set, or one of them is wrong.
108
108
  true;
@@ -607,41 +607,41 @@ function validateDestinationDirectory(value, path, into) {
607
607
  into.filled(destination.label, "task.destination.label", `${at}.label`, "a destination needs a label");
608
608
  });
609
609
  }
610
- function validateDispositions(value, path, into) {
610
+ function validateOutcomes(value, path, into) {
611
611
  if (value === true)
612
612
  return;
613
613
  if (!isPlainObject(value)) {
614
- into.add("task.dispositions.shape", path, "must be true or a disposition policy");
614
+ into.add("task.outcomes.shape", path, "must be true or an outcome policy");
615
615
  return;
616
616
  }
617
617
  if (value.required !== undefined) {
618
- into.require(typeof value.required === "boolean", "task.dispositions.required", `${path}.required`, "required must be a boolean when present");
618
+ into.require(typeof value.required === "boolean", "task.outcomes.required", `${path}.required`, "required must be a boolean when present");
619
619
  }
620
620
  // A code must be collected before completion, so there must be one to collect.
621
621
  if (value.required === true && !(Array.isArray(value.codes) && value.codes.length > 0)) {
622
- into.add("task.dispositions.required.codes", `${path}.codes`, "a required disposition policy must publish at least one code");
622
+ into.add("task.outcomes.required.codes", `${path}.codes`, "a required outcome policy must publish at least one code");
623
623
  }
624
624
  if (value.notes !== undefined)
625
- into.oneOf(value.notes, NOTES_RULES, "task.dispositions.notes", `${path}.notes`);
625
+ into.oneOf(value.notes, NOTES_RULES, "task.outcomes.notes", `${path}.notes`);
626
626
  if (value.codes === undefined)
627
627
  return;
628
628
  if (!Array.isArray(value.codes)) {
629
- into.add("task.dispositions.codes", `${path}.codes`, "codes must be an array when present");
629
+ into.add("task.outcomes.codes", `${path}.codes`, "codes must be an array when present");
630
630
  return;
631
631
  }
632
632
  const seen = new Set();
633
633
  value.codes.forEach((code, index) => {
634
634
  const at = `${path}.codes[${index}]`;
635
635
  if (!isPlainObject(code)) {
636
- into.add("task.disposition.shape", at, "each disposition code must be an object");
636
+ into.add("task.outcome.shape", at, "each outcome code must be an object");
637
637
  return;
638
638
  }
639
- if (into.filled(code.id, "task.disposition.id", `${at}.id`, "a disposition code needs an id")) {
639
+ if (into.filled(code.id, "task.outcome.id", `${at}.id`, "an outcome code needs an id")) {
640
640
  if (seen.has(code.id))
641
- into.add("task.disposition.unique", `${at}.id`, `duplicate disposition code: ${code.id}`);
641
+ into.add("task.outcome.unique", `${at}.id`, `duplicate outcome code: ${code.id}`);
642
642
  seen.add(code.id);
643
643
  }
644
- into.filled(code.label, "task.disposition.label", `${at}.label`, "a disposition code needs a label");
644
+ into.filled(code.label, "task.outcome.label", `${at}.label`, "an outcome code needs a label");
645
645
  });
646
646
  }
647
647
  function validateCustomCapabilities(value, path, into) {
@@ -697,7 +697,7 @@ const DEFAULT_LEVEL_IDS = DEFAULT_LEVELS.map(level => level.id);
697
697
  const POLICY_SETTINGS = membersOf({ on: true, off: true, person: true });
698
698
  const POLICY_KEYS = new Set([
699
699
  ...TASK_CAPABILITIES.voice, ...TASK_CAPABILITIES.chat, ...TASK_CAPABILITIES.email, "dial",
700
- ].filter(name => name !== "browsers" && name !== "dispositions" && name !== "custom"));
700
+ ].filter(name => name !== "browsers" && name !== "outcomes" && name !== "custom"));
701
701
  const PERSON_SETTABLE = /^(hold|skill:.+)$/;
702
702
  const isLocked = (value) => isPlainObject(value) && value.lockedBy !== undefined;
703
703
  /** The level ids in force: the manifest's, or the defaults when the caller holds no manifest. */
@@ -1260,9 +1260,9 @@ function validateTaskInto(task, context, path, into) {
1260
1260
  into.add("task.capability.dialOutcomes.required", `${path}.capabilities.${name}`, `${name} dials, and the manifest declares no dialOutcomes to say how a dial ends`);
1261
1261
  }
1262
1262
  // A control the queue could allow may stand locked in its place, saying whose. What the
1263
- // queue provides -- browsers, dispositions, custom controls -- is content, not a control.
1263
+ // queue provides -- browsers, outcomes, custom controls -- is content, not a control.
1264
1264
  if (isLocked(declared)) {
1265
- if (into.require(name !== "browsers" && name !== "dispositions" && name !== "custom", "task.capability.locked.unexpected", `${path}.capabilities.${name}`, `${name} is what the queue provides, not a control anyone locks`)) {
1265
+ if (into.require(name !== "browsers" && name !== "outcomes" && name !== "custom", "task.capability.locked.unexpected", `${path}.capabilities.${name}`, `${name} is what the queue provides, not a control anyone locks`)) {
1266
1266
  validateLockedInto(declared, "task.capability.locked", `${path}.capabilities.${name}`, context.levels, into);
1267
1267
  }
1268
1268
  continue;
@@ -1271,8 +1271,8 @@ function validateTaskInto(task, context, path, into) {
1271
1271
  case "recording":
1272
1272
  into.violations.push(...validateRecordingPolicy(declared, `${path}.capabilities.recording`));
1273
1273
  break;
1274
- case "dispositions":
1275
- validateDispositions(declared, `${path}.capabilities.dispositions`, into);
1274
+ case "outcomes":
1275
+ validateOutcomes(declared, `${path}.capabilities.outcomes`, into);
1276
1276
  break;
1277
1277
  case "custom":
1278
1278
  validateCustomCapabilities(declared, `${path}.capabilities.custom`, into);
@@ -1348,9 +1348,9 @@ export function validateBreakStatus(state, tasks, path = "snapshot") {
1348
1348
  return into.violations;
1349
1349
  }
1350
1350
  /**
1351
- * Checks lead break dispatch using current authentication, transport, roster (`team`) and,
1351
+ * Checks lead break dispatch using current authentication, transport, team member list (`team`) and,
1352
1352
  * for place/release, the target's full `memberBreak`. The provider must authorize the target
1353
- * and recheck the decision atomically; a roster is not authority to act after it has changed.
1353
+ * and recheck the decision atomically; a team member list is not authority to act after it has changed.
1354
1354
  */
1355
1355
  export function validateTeamBreakCommand(request, context, path = "teamBreakCommand") {
1356
1356
  const into = new Collector();
@@ -1388,7 +1388,7 @@ export function validateTeamBreakCommand(request, context, path = "teamBreakComm
1388
1388
  const members = Array.isArray(team.members) ? team.members : [];
1389
1389
  const member = members.find((m) => isPlainObject(m) && m.id === command.memberId);
1390
1390
  const self = isPlainObject(auth.identity) ? auth.identity.id : undefined;
1391
- into.require(isPlainObject(member) && command.memberId !== self, "team.break.command.member", path, "the target must be another member of the current authorized roster");
1391
+ into.require(isPlainObject(member) && command.memberId !== self, "team.break.command.member", path, "the target must be another member of the current authorized team member list");
1392
1392
  if (command.type === "decide") {
1393
1393
  into.require(command.decision === "granted" || command.decision === "denied", "team.break.command.decision", path, "decide granted or denied");
1394
1394
  into.require(isPlainObject(member) && member.break === "awaiting-decision", "team.break.command.awaiting", path, "decide only a currently awaiting-decision request");
@@ -1562,38 +1562,38 @@ function validateBreakState(value, path, into) {
1562
1562
  into.require(typeof value.activeReasonId === "string" && value.activeReasonId.length > 0, "break.activeReasonId.required", `${path}.activeReasonId`, `a break ${describeValue(value.approval)} on a provider that publishes reasons names the one it is on`);
1563
1563
  }
1564
1564
  }
1565
- export function validateTeamRoster(roster, path = "team", context = {}) {
1565
+ export function validateTeamMembers(teamMembers, path = "team", context = {}) {
1566
1566
  const into = new Collector();
1567
- validateTeamRosterInto(roster, path, context, into);
1567
+ validateTeamMembersInto(teamMembers, path, context, into);
1568
1568
  return into.violations;
1569
1569
  }
1570
- function validateTeamRosterInto(roster, path, context, into) {
1571
- if (!isPlainObject(roster)) {
1572
- into.add("team.shape", path, "a team roster must be an object");
1570
+ function validateTeamMembersInto(teamMembers, path, context, into) {
1571
+ if (!isPlainObject(teamMembers)) {
1572
+ into.add("team.shape", path, "a team member list must be an object");
1573
1573
  return;
1574
1574
  }
1575
- // The login is the permission: a roster reaches a login that declares `capabilities.team` and
1575
+ // The login is the permission: a team member list reaches a login that declares `capabilities.team` and
1576
1576
  // nobody else. Only a caller holding the login can check it.
1577
1577
  if (context.capabilities !== undefined && context.capabilities.team === undefined) {
1578
- into.add("team.unentitled", path, "a roster published to a login that does not declare capabilities.team: the login is the permission");
1578
+ into.add("team.unentitled", path, "a team member list published to a login that does not declare capabilities.team: the login is the permission");
1579
1579
  }
1580
- // The team's policies travel with the roster exactly when the login may set them.
1580
+ // The team's policies travel with the team member list exactly when the login may set them.
1581
1581
  if (context.capabilities !== undefined) {
1582
1582
  const may = context.capabilities.team?.policyControl === true;
1583
- if (may && roster.policies === undefined) {
1584
- into.add("team.policies.required", `${path}.policies`, "the login declares team.policyControl, so the roster carries the team's policies");
1583
+ if (may && teamMembers.policies === undefined) {
1584
+ into.add("team.policies.required", `${path}.policies`, "the login declares team.policyControl, so the team member list carries the team's policies");
1585
1585
  }
1586
- if (!may && roster.policies !== undefined) {
1586
+ if (!may && teamMembers.policies !== undefined) {
1587
1587
  into.add("team.policies.capability", `${path}.policies`, "policies require team.policyControl on the login: a lead who may not set them has nothing to see");
1588
1588
  }
1589
1589
  }
1590
- if (roster.policies !== undefined)
1591
- validateTeamPoliciesInto(roster.policies, `${path}.policies`, into, context.levels);
1592
- if (roster.requests === undefined) {
1590
+ if (teamMembers.policies !== undefined)
1591
+ validateTeamPoliciesInto(teamMembers.policies, `${path}.policies`, into, context.levels);
1592
+ if (teamMembers.requests === undefined) {
1593
1593
  // `[]` says nobody is asking; omission says the lead may not be asked. A login that may be
1594
1594
  // asked therefore always carries the list.
1595
1595
  if (context.capabilities?.team?.leadAssistControl === true) {
1596
- into.add("team.requests.required", `${path}.requests`, "the login declares team.leadAssistControl, so the roster carries requests: [] when nobody is asking");
1596
+ into.add("team.requests.required", `${path}.requests`, "the login declares team.leadAssistControl, so the team member list carries requests: [] when nobody is asking");
1597
1597
  }
1598
1598
  }
1599
1599
  else {
@@ -1602,12 +1602,12 @@ function validateTeamRosterInto(roster, path, context, into) {
1602
1602
  if (context.capabilities !== undefined) {
1603
1603
  into.require(context.capabilities.team?.leadAssistControl === true, "team.requests.capability", `${path}.requests`, "requests require team.leadAssistControl on the login: a lead who may not join has nothing to decide");
1604
1604
  }
1605
- if (!Array.isArray(roster.requests)) {
1605
+ if (!Array.isArray(teamMembers.requests)) {
1606
1606
  into.add("team.requests.shape", `${path}.requests`, "requests must be an array when present");
1607
1607
  }
1608
1608
  else {
1609
1609
  const seenRequests = new Set();
1610
- roster.requests.forEach((request, index) => {
1610
+ teamMembers.requests.forEach((request, index) => {
1611
1611
  const at = `${path}.requests[${index}]`;
1612
1612
  if (!isPlainObject(request)) {
1613
1613
  into.add("team.request.shape", at, "each request must be an object");
@@ -1619,7 +1619,7 @@ function validateTeamRosterInto(roster, path, context, into) {
1619
1619
  seenRequests.add(request.id);
1620
1620
  }
1621
1621
  if (into.require(isUserId(request.memberId), "team.request.memberId", `${at}.memberId`, "a request names the member asking")) {
1622
- into.require(request.memberId !== context.self, "team.request.self", `${at}.memberId`, "the roster carries the reader's own ask: an agent's request for a lead goes to whoever leads them");
1622
+ into.require(request.memberId !== context.self, "team.request.self", `${at}.memberId`, "the team member list carries the reader's own ask: an agent's request for a lead goes to whoever leads them");
1623
1623
  }
1624
1624
  into.require(isTaskId(request.taskId), "team.request.taskId", `${at}.taskId`, "a request names the task the lead would join");
1625
1625
  into.require(isAllocationId(request.allocationId), "team.request.allocationId", `${at}.allocationId`, "a request names the task by its allocation too");
@@ -1629,22 +1629,22 @@ function validateTeamRosterInto(roster, path, context, into) {
1629
1629
  });
1630
1630
  }
1631
1631
  }
1632
- if (!Array.isArray(roster.members)) {
1633
- into.add("team.members.shape", `${path}.members`, "a roster must carry a members array");
1632
+ if (!Array.isArray(teamMembers.members)) {
1633
+ into.add("team.members.shape", `${path}.members`, "a team member list must carry a members array");
1634
1634
  return;
1635
1635
  }
1636
1636
  const seen = new Set();
1637
- roster.members.forEach((member, index) => {
1637
+ teamMembers.members.forEach((member, index) => {
1638
1638
  const at = `${path}.members[${index}]`;
1639
1639
  if (!isPlainObject(member)) {
1640
- into.add("team.member.shape", at, "each roster member must be an object");
1640
+ into.add("team.member.shape", at, "each team member must be an object");
1641
1641
  return;
1642
1642
  }
1643
- if (into.require(isUserId(member.id), "team.member.id", `${at}.id`, "a roster member needs a user id")) {
1643
+ if (into.require(isUserId(member.id), "team.member.id", `${at}.id`, "a team member needs a user id")) {
1644
1644
  if (seen.has(member.id))
1645
- into.add("team.member.unique", `${at}.id`, `duplicate roster member: ${member.id}`);
1645
+ into.add("team.member.unique", `${at}.id`, `duplicate team member: ${member.id}`);
1646
1646
  seen.add(member.id);
1647
- into.require(member.id !== context.self, "team.member.self", `${at}.id`, "the roster carries the agent it is published to: a lead does not report to themself");
1647
+ into.require(member.id !== context.self, "team.member.self", `${at}.id`, "the team member list carries the agent it is published to: a lead does not report to themself");
1648
1648
  }
1649
1649
  into.oneOf(member.availability, TEAM_AVAILABILITIES, "team.member.availability", `${at}.availability`);
1650
1650
  if (member.since !== undefined)
@@ -1752,13 +1752,13 @@ export function validateSnapshot(snapshot, manifest, path = "snapshot", context
1752
1752
  into.add("snapshot.calendar.shape", `${path}.scheduledActivities`, "scheduledActivities must be an array when present");
1753
1753
  }
1754
1754
  }
1755
- // The login is the permission: a lead's snapshot carries a roster, `[]` included. The other
1756
- // direction -- a roster to a login that does not lead -- is the roster's own rule.
1755
+ // The login is the permission: a lead's snapshot carries a team member list, `[]` included. The other
1756
+ // direction -- a team member list to a login that does not lead -- is the team member list's own rule.
1757
1757
  if (context.capabilities?.team !== undefined && snapshot.team === undefined) {
1758
- into.add("team.required", `${path}.team`, "the login declares capabilities.team, so every snapshot carries a roster: [] when nobody is in it");
1758
+ into.add("team.required", `${path}.team`, "the login declares capabilities.team, so every snapshot carries a team member list: [] when nobody is in it");
1759
1759
  }
1760
1760
  if (snapshot.team !== undefined)
1761
- validateTeamRosterInto(snapshot.team, `${path}.team`, { ...context, levels }, into);
1761
+ validateTeamMembersInto(snapshot.team, `${path}.team`, { ...context, levels }, into);
1762
1762
  return into.violations;
1763
1763
  }
1764
1764
  // ---------------------------------------------------------------------------
@@ -1949,7 +1949,7 @@ export function validateEventEnvelope(envelope, manifest, path = "event", contex
1949
1949
  validateQueueSummary(event.summary, `${at}.summary`, into);
1950
1950
  break;
1951
1951
  case "team-updated":
1952
- validateTeamRosterInto(event.team, `${at}.team`, { ...context, levels }, into);
1952
+ validateTeamMembersInto(event.team, `${at}.team`, { ...context, levels }, into);
1953
1953
  break;
1954
1954
  case "contacts-updated":
1955
1955
  into.require(idle.contacts === true, "event.contacts.capability", `${at}.contacts`, "contacts-updated requires the contacts idle capability");
@@ -2298,7 +2298,7 @@ export function validateTaskCommand(command, task, path = "command", taskContext
2298
2298
  fields.push("dialId");
2299
2299
  break;
2300
2300
  case "complete":
2301
- fields.push("disposition", "notes");
2301
+ fields.push("outcome", "notes");
2302
2302
  break;
2303
2303
  case "transfer":
2304
2304
  fields.push("action");
@@ -2359,8 +2359,8 @@ export function validateTaskCommand(command, task, path = "command", taskContext
2359
2359
  into.violations.push(...validateRecordingCommandShape(command, "provider", path));
2360
2360
  break;
2361
2361
  case "complete":
2362
- if (command.disposition !== undefined)
2363
- into.filled(command.disposition, "command.complete.disposition", `${path}.disposition`, "a disposition must not be empty when present");
2362
+ if (command.outcome !== undefined)
2363
+ into.filled(command.outcome, "command.complete.outcome", `${path}.outcome`, "an outcome must not be empty when present");
2364
2364
  if (command.notes !== undefined)
2365
2365
  into.require(typeof command.notes === "string", "command.complete.notes", `${path}.notes`, "notes must be a string when present");
2366
2366
  break;
@@ -2509,27 +2509,27 @@ export function validateTaskCommand(command, task, path = "command", taskContext
2509
2509
  }
2510
2510
  case "complete": {
2511
2511
  into.require(task.completionMode === "agent-command", "command.complete.mode", path, "complete belongs to agent-command; a provider-automatic task completes itself");
2512
- // What travels with complete is what the dispositions capability published: a code from its
2512
+ // What travels with complete is what the outcomes capability published: a code from its
2513
2513
  // list where it has one, a code at all where it requires one, notes as it said, nothing where it published nothing.
2514
- const dispositions = capabilities.dispositions;
2515
- if (dispositions === undefined) {
2516
- into.require(command.disposition === undefined, "command.complete.disposition.unexpected", `${path}.disposition`, "the task declares no dispositions; complete travels with no code");
2517
- into.require(command.notes === undefined, "command.complete.notes.unexpected", `${path}.notes`, "the task declares no dispositions; complete travels with no notes");
2514
+ const outcomes = capabilities.outcomes;
2515
+ if (outcomes === undefined) {
2516
+ into.require(command.outcome === undefined, "command.complete.outcome.unexpected", `${path}.outcome`, "the task declares no outcomes; complete travels with no code");
2517
+ into.require(command.notes === undefined, "command.complete.notes.unexpected", `${path}.notes`, "the task declares no outcomes; complete travels with no notes");
2518
2518
  }
2519
- else if (dispositions === true) {
2520
- into.require(command.disposition === undefined, "command.complete.disposition.unexpected", `${path}.disposition`, "a bare dispositions control publishes no code, so complete carries none");
2519
+ else if (outcomes === true) {
2520
+ into.require(command.outcome === undefined, "command.complete.outcome.unexpected", `${path}.outcome`, "a bare outcomes control publishes no code, so complete carries none");
2521
2521
  }
2522
- else if (isPlainObject(dispositions)) {
2523
- if (Array.isArray(dispositions.codes) && command.disposition !== undefined) {
2524
- into.require(dispositions.codes.some(code => isPlainObject(code) && code.id === command.disposition), "command.complete.disposition.unknown", `${path}.disposition`, `${describeValue(command.disposition)} is not a code the task published`);
2522
+ else if (isPlainObject(outcomes)) {
2523
+ if (Array.isArray(outcomes.codes) && command.outcome !== undefined) {
2524
+ into.require(outcomes.codes.some(code => isPlainObject(code) && code.id === command.outcome), "command.complete.outcome.unknown", `${path}.outcome`, `${describeValue(command.outcome)} is not a code the task published`);
2525
2525
  }
2526
- if (dispositions.required === true) {
2527
- into.require(command.disposition !== undefined, "command.complete.disposition.required", `${path}.disposition`, "the task requires a disposition and complete carries none");
2526
+ if (outcomes.required === true) {
2527
+ into.require(command.outcome !== undefined, "command.complete.outcome.required", `${path}.outcome`, "the task requires an outcome and complete carries none");
2528
2528
  }
2529
- if (dispositions.notes === "none") {
2529
+ if (outcomes.notes === "none") {
2530
2530
  into.require(command.notes === undefined, "command.complete.notes.unexpected", `${path}.notes`, "the task takes no notes");
2531
2531
  }
2532
- else if (dispositions.notes === "required") {
2532
+ else if (outcomes.notes === "required") {
2533
2533
  into.require(typeof command.notes === "string" && command.notes.length > 0, "command.complete.notes.required", `${path}.notes`, "the task requires notes and complete carries none");
2534
2534
  }
2535
2535
  }