@xema/omni-protocol 0.1.85 → 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
  /**
@@ -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,7 +4,7 @@ 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
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"];
@@ -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 = [
@@ -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));
@@ -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
  /**
@@ -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");
package/guide.md CHANGED
@@ -23,7 +23,7 @@ are used precisely throughout and mean nothing looser here.
23
23
  | **Provider** | One independently connected external system: a voice platform, a chat platform, a mail platform. |
24
24
  | **Adapter** | The package implementing this contract for one provider. One adapter is one provider, so the words are often interchangeable; *provider* names the system, *adapter* the code speaking for it. |
25
25
  | **Agent** | The person signed in and taking work. On `onCall`, `agent` is a person on the call by user id; transfer destinations may include an agent when the provider publishes that directory item. |
26
- | **Lead** | An agent whose login declares `capabilities.team`. The provider publishes a `TeamRoster` to them and to nobody else: **the login is the permission**. |
26
+ | **Lead** | An agent whose login declares `capabilities.team`. The provider publishes a `TeamMembers` object to them and to nobody else: **the login is the permission**. |
27
27
  | **Provisioning** | Omni-side policy about this agent, configured outside the protocol and never sent to a provider. It gates whether an offer may be rejected, whether the agent goes ready on login, and whether tasks are auto-accepted. Where a capability and provisioning disagree, the stricter wins. |
28
28
  | **Call** | The caller’s complete phone call, which may continue through IVRs, queues and several agents. |
29
29
  | **Interaction** | One agent’s part in the call or conversation. The same call may return to that agent for a new interaction. |
@@ -38,9 +38,9 @@ are used precisely throughout and mean nothing looser here.
38
38
  | **Snapshot** | The provider's complete state at one moment. It replaces what Omni holds; it is never a patch. |
39
39
  | **Event** | One completed transaction reported after a snapshot established the baseline. |
40
40
  | **Break** | A reported, supervised state in which the agent is not working — one with a reason, a decision behind it and a return. It covers what a platform may call *not-ready*, including equipment trouble. An agent who is merely at capacity is not on a break. |
41
- | **Workspace** | What Omni shows the agent. The **task workspace** holds the selected task, its controls and its browsers; the **idle workspace** holds what a provider contributes when no task is selected — dialpad, contacts, calendar, roster. |
41
+ | **Workspace** | What Omni shows the agent. The **task workspace** holds the selected task, its controls and its browsers; the **idle workspace** holds what a provider contributes when no task is selected — dialpad, contacts, calendar, team member list. |
42
42
  | **Dial** | One outbound call the host asks a provider to place — from the idle dialpad, a cold or warm transfer, a conference add, or a connect-back. Identified by the host's `dialId`, accepted as `dialling`, and ended by exactly one `dial-outcome`. See **Every dial has an outcome**. |
43
- | **Monitor** | A lead listening to a member's call unasked, from the roster: `monitor` in silence, `whisper` heard by the agent alone, `barge` heard by everyone. Nothing of it reaches the member's task, and there is no take-over in it. See **Monitoring a call**. |
43
+ | **Monitor** | A lead listening to a member's call unasked, from the team member list: `monitor` in silence, `whisper` heard by the agent alone, `barge` heard by everyone. Nothing of it reaches the member's task, and there is no take-over in it. See **Monitoring a call**. |
44
44
  | **On the call** | Who a voice task's audio joins, or is bringing in, as the provider states it on `Task.onCall`: the party, the agents, and anyone consulted or conferenced in from the moment their dial is placed. |
45
45
 
46
46
  Six words describe *what state a thing is in*, and they are not interchangeable: each belongs to
@@ -54,7 +54,7 @@ transport, a task, a break and a call each have a word of their own:
54
54
  | `transport` | A connection | `connecting`, `active`, `error` |
55
55
  | `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
56
56
  | `approval` | A break request | `not-requested`, `awaiting-decision`, `granted`, `starting-after-task`, `in-effect` |
57
- | `availability` | A roster member | `ready`, `on-task`, `on-break`, `signed-out` |
57
+ | `availability` | A team member | `ready`, `on-task`, `on-break`, `signed-out` |
58
58
 
59
59
  ## Versioning
60
60
 
@@ -120,7 +120,7 @@ identifier of its own for an agent or a manager, and none crosses this boundary
120
120
  operating-system account, not a directory identity, not a licence.
121
121
 
122
122
  So a `UserId` means nothing outside the provider that issued it. Provider A's
123
- `interactionHistory[].by` and provider B's roster `memberId` are unrelated strings that will
123
+ `interactionHistory[].by` and provider B's team member list `memberId` are unrelated strings that will
124
124
  eventually collide, and one person on several providers has several identities that nothing here
125
125
  pairs. Scope every user identifier with its provider ID before storing or comparing it, exactly as
126
126
  `taskKey()` already does for tasks — see `userKey()` under **Utilities**.
@@ -485,7 +485,7 @@ type Snapshot = {
485
485
  taskCount: number;
486
486
  contacts?: Contact[];
487
487
  scheduledActivities?: ScheduledActivity[];
488
- team?: TeamRoster;
488
+ team?: TeamMembers;
489
489
  };
490
490
 
491
491
  type AgentCapacity = {
@@ -901,11 +901,11 @@ or email states those values in `lockedValues`, since a run that cannot ask the
901
901
  pass. A host never has the value and never asks. A name is not locked. What the queue provides rather than permits — browsers, outcomes, custom
902
902
  controls — is content, and is never locked.
903
903
 
904
- **A lead sets the team's policy from their roster.** A login that declares
904
+ **A lead sets the team's policy from their team member list.** A login that declares
905
905
  `capabilities.team.policyControl` may `executeTeamPolicy({ type: "set", capability, setting })`
906
906
  with `on`, `off`, or `person`, for any task control, `dial`, or a skill — and only `hold`
907
907
  and skills may be `person`; connect back and new call are the team's, on or off, within what the queue
908
- allows. The roster carries `policies` for such a login: every policy as it stands, who set it, and
908
+ allows. The team member list carries `policies` for such a login: every policy as it stands, who set it, and
909
909
  `lockedBy` where a level above the team made it theirs to keep, which the lead sees and cannot
910
910
  change — `executeTeamPolicy` on it answers `failed` with `omni.capability-not-enabled`.
911
911
 
@@ -1002,7 +1002,7 @@ type LeadRequest = {
1002
1002
  since: IsoTimestamp;
1003
1003
  };
1004
1004
 
1005
- type TeamRoster = {
1005
+ type TeamMembers = {
1006
1006
  members: TeamMember[];
1007
1007
  requests?: LeadRequest[];
1008
1008
  policies?: TeamPolicies;
@@ -1115,7 +1115,7 @@ type ProviderEvent =
1115
1115
  | { type: "announcement"; text: string; html?: string; announcedAt: IsoTimestamp; expiresAt?: IsoTimestamp }
1116
1116
  | { type: "queue-summary"; summary: QueueSummary }
1117
1117
  | { type: "diagnostic"; expected: string; observed: string; taskId?: TaskId; allocationId?: AllocationId }
1118
- | { type: "team-updated"; team: TeamRoster }
1118
+ | { type: "team-updated"; team: TeamMembers }
1119
1119
  | { type: "contacts-updated"; contacts: Contact[] }
1120
1120
  | { type: "calendar-updated"; scheduledActivities: ScheduledActivity[] };
1121
1121
 
@@ -1923,11 +1923,11 @@ them from what arrives later.
1923
1923
  | Field | Contract |
1924
1924
  | --- | --- |
1925
1925
  | `breaks` | This login may request a break. Requires the four break methods on the connection. |
1926
- | `team` | This login leads a team. The provider publishes a `TeamRoster` to it on every snapshot — `[]` when nobody is in it — and to nobody else. |
1926
+ | `team` | This login leads a team. The provider publishes a `TeamMembers` object to it on every snapshot — `members: []` when nobody is in it — and to nobody else. |
1927
1927
  | `team.breakControl` | This lead may act on their team's breaks through `executeTeamBreak` — place, release, decide, set policy — as far as the provider supports; a command it lacks answers `omni.capability-not-enabled`. Omni asks for a decision only against a member whose `break` is `awaiting-decision`, so a provider that grants on request is never asked to decide. Requires `executeTeamBreak`. |
1928
1928
  | `team.leadAssistControl` | This lead may join a member's call on request. Requires `executeTeamLeadAssist`. |
1929
1929
  | `team.monitorControl` | This lead may listen to a member's call unasked, in the listed modes and no others: `monitor`, `whisper`, `barge`. The list always includes `monitor`, since the other two begin from one. Requires `executeTeamMonitor`. See **Monitoring a call**. |
1930
- | `team.policyControl` | This lead sets the team's policy per capability — on, off, or the person's — within what the queue allows. Requires `executeTeamPolicy`; the roster carries `policies`. |
1930
+ | `team.policyControl` | This lead sets the team's policy per capability — on, off, or the person's — within what the queue allows. Requires `executeTeamPolicy`; the team member list carries `policies`. |
1931
1931
  | `preferences` | What the team left to this person, with where each stands and who set it. Omitted when nothing was. Requires `setPreference`. See **Who decides what an agent may do**. |
1932
1932
 
1933
1933
  A session action is available only when both the capability and Omni provisioning permit it.
@@ -1937,7 +1937,7 @@ state. A provider that reads roles live — a lead demoted mid-shift — republi
1937
1937
  with the new set through `subscribe()` on the authentication session, which Omni keeps open for
1938
1938
  the life of the connection for exactly this reason, and the next snapshot agrees with it. Omni
1939
1939
  provisions what the capabilities call for at sign-in — a team panel for a lead, empty until the
1940
- roster arrives, and nothing for anybody else — and withdraws it on the next render when the
1940
+ team member list arrives, and nothing for anybody else — and withdraws it on the next render when the
1941
1941
  capability goes. A command that arrives after its capability was withdrawn is answered `failed`
1942
1942
  with `omni.capability-not-enabled`: the provider names it, so Omni never has to infer from a
1943
1943
  capability change it may not have rendered yet that "you are no longer a lead" is the message
@@ -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 roster member, the manager on an imposed break, the agent on an interaction step —
2026
+ provider names — a team member, the manager on an imposed 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 |
@@ -2136,7 +2136,7 @@ a capability it agrees with the login: a lead's snapshot carries `team`, nobody
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. |
2138
2138
  | `scheduledActivities` | Required complete calendar contribution when the manifest declares `calendar`; `[]` clears it. Omitted only when it does not. |
2139
- | `team` | Required `TeamRoster` when the login declares `capabilities.team`, `[]` when nobody is in it. Forbidden otherwise — the login is the permission. |
2139
+ | `team` | Required `TeamMembers` when the login declares `capabilities.team`, `members: []` when nobody is in it. Forbidden otherwise — the login is the permission. |
2140
2140
 
2141
2141
  ## Live connection
2142
2142
 
@@ -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 roster, 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 `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. |
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,14 +2211,14 @@ 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 roster, or
2214
+ Required of any adapter that publishes a `UserId` — on `ImposedBreak.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
 
2218
2218
  - **Omit an id you cannot resolve; do not invent a name for it.** A missing entry says *I do not
2219
2219
  know this person*, which Omni renders as such. Ordering is not significant and the response may
2220
2220
  be shorter than the request.
2221
- - **Take the whole list in one call.** Omni resolves a roster or an interaction history as a batch, and
2221
+ - **Take the whole list in one call.** Omni resolves a team member list or an interaction history as a batch, and
2222
2222
  a per-id round trip multiplies that by its length.
2223
2223
  - **Omni caches a result for one hour, then resolves it again.** The identifier is stable across
2224
2224
  logins but the name behind it is not, so the cache expires on a clock rather than living for the
@@ -2857,7 +2857,7 @@ Four rules a provider has to keep:
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
2859
  the same way `ImposedBreak.by` does. It comes from this provider's own directory, the same
2860
- namespace as `AuthenticationState.identity.id` and the team roster, so entries pair
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
2863
2863
  called Arun on one site is ordinary, and anything pairing entries on a display name pairs them
@@ -3726,7 +3726,7 @@ Your own tasks are the only ones you count. What the agent holds at other provid
3726
3726
  concern — Omni set `count` knowing it, and this is how: the agent is one person on several
3727
3727
  providers, and the host divides their capacity among them rather than telling each the whole. A
3728
3728
  provider that has none of it for now is told **`count: 0`, host-stopped**: allocate nothing, show
3729
- the member as `reserved` on the roster -- signed in here, capacity held by the host for elsewhere,
3729
+ the member as `reserved` on the team member list -- signed in here, capacity held by the host for elsewhere,
3730
3730
  a fact this provider holds, where `on-task` would assert work it cannot see -- and take the next
3731
3731
  count as any other when the host has capacity for this provider again. Zero is the one restatement
3732
3732
  that follows work rather than provisioning.
@@ -3764,7 +3764,7 @@ request object only to the request method, and undefined to the other three.
3764
3764
 
3765
3765
  Use `validateTeamBreakCommand(request, context)` for lead decisions, placement, release and
3766
3766
  policy commands. It requires the live lead capability and active transport, a current target
3767
- roster for member commands, and the target's complete break state for placement/release.
3767
+ team member list for member commands, and the target's complete break state for placement/release.
3768
3768
  Approve/deny requires an awaiting decision; placement uses the target's reason codes; release
3769
3769
  requires an imposed committed break. The context's target state must belong to the named member;
3770
3770
  that association and backend authorization are provider responsibilities.
@@ -4026,19 +4026,27 @@ so there is nothing on it to resume.
4026
4026
  ## Team leads
4027
4027
 
4028
4028
  A lead who also takes calls sees their team on the idle dashboard. `Snapshot.team` carries a
4029
- `TeamRoster`, replaced whole by `team-updated`.
4029
+ `TeamMembers` object, replaced whole by `team-updated`.
4030
+
4031
+ Migration: TeamRoster is now `TeamMembers`, and validateTeamRoster is now
4032
+ `validateTeamMembers`. Update type imports and validator calls; no legacy export aliases exist.
4033
+ The object still contains `members` and the permitted `requests` and `policies`; it is not a
4034
+ bare array. `Snapshot.team`, the `team-updated` event and its `team` payload, login permissions,
4035
+ and `team.*` validation rules keep their names and semantics. An empty team has `members: []`;
4036
+ an absent `team` still means the login is not entitled to that contribution.
4037
+
4030
4038
 
4031
4039
  | Field | Contract |
4032
4040
  | --- | --- |
4033
- | `members` | Every member of this lead's team, whatever their state. `[]` says the lead has a team with nobody in it; omitting the roster says something else entirely — see **The login is the permission** below. |
4041
+ | `members` | Every member of this lead's team, whatever their state. `[]` says the lead has a team with nobody in it; omitting the team member list says something else entirely — see **The login is the permission** below. |
4034
4042
  | `requests` | The members currently asking this lead to join a call, each with the task and the note. Required when the login declares `team.leadAssistControl`, `[]` when nobody is asking; omitted when it does not. See **Lead assist**. |
4035
4043
  | `policies` | The team's policy per capability as it stands — the setting, who set it, and `lockedBy` where a level above the team made it theirs to keep. Required when the login declares `team.policyControl`; omitted when it does not. See **Who decides what an agent may do**. |
4036
4044
 
4037
4045
  | `TeamMember` field | Contract |
4038
4046
  | --- | --- |
4039
- | `id` | Required `UserId`. A task carries no names and neither does a roster: Omni resolves what to display with `describeUsers()`. |
4047
+ | `id` | Required `UserId`. A task carries no names and neither does a team member list: Omni resolves what to display with `describeUsers()`. |
4040
4048
  | `availability` | Required. What the member is doing now. |
4041
- | `since` | Optional. When the current `availability` began — not when they signed in, and not when the roster was read. |
4049
+ | `since` | Optional. When the current `availability` began — not when they signed in, and not when the team member list was read. |
4042
4050
  | `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
4043
4051
 
4044
4052
  Each availability value means one thing:
@@ -4051,7 +4059,7 @@ Each availability value means one thing:
4051
4059
  | `reserved` | Signed in here, and the host holds this agent's capacity for another provider (`count: 0`, host-stopped): not receiving this provider's work, and not on a break. See **Capacity**. |
4052
4060
  | `signed-out` | Known to this team but not signed in to this provider. |
4053
4061
 
4054
- **Always publish the complete roster, never a change to it.** Team presence typically reaches an
4062
+ **Always publish the complete team member list, never a change to it.** Team presence typically reaches an
4055
4063
  adapter over a best-effort channel with no ordering and no delivery guarantee, so a stream of deltas
4056
4064
  cannot be trusted to reconstruct the truth. The adapter reconciles against its own authoritative
4057
4065
  read and publishes the result.
@@ -4062,15 +4070,15 @@ everybody — worse than showing nothing, because it looks like data. Send it on
4062
4070
  knows when the state actually began. It times the current `availability`, so it moves every time
4063
4071
  that value does.
4064
4072
 
4065
- **The login is the permission.** A roster goes to a login that declares `capabilities.team`, on
4073
+ **The login is the permission.** A team member list goes to a login that declares `capabilities.team`, on
4066
4074
  every snapshot, and to nobody else. Omni never decides who leads a team: the provider said so at
4067
- sign-in, and the roster agrees with it — present, `[]` included, for a lead; absent for everybody
4075
+ sign-in, and the team member list agrees with it — present, `[]` included, for a lead; absent for everybody
4068
4076
  else, which is the correct rendering for an agent who leads nobody. What the lead may do with the
4069
- roster is on the login too, `team.breakControl` and `team.leadAssistControl`, never on the roster.
4077
+ team member list is on the login too, `team.breakControl` and `team.leadAssistControl`, never on the team member list.
4070
4078
 
4071
- **The roster never carries the agent it is published to — not in `members`, and not in
4079
+ **The team member list never carries the agent it is published to — not in `members`, and not in
4072
4080
  `requests`.** A lead does not report to themself: their own break request and their own ask for a
4073
- lead go up to whoever leads them and appear on *that* person's roster, while the requester sees
4081
+ lead go up to whoever leads them and appear on *that* person's team member list, while the requester sees
4074
4082
  only their own `BreakState` and their task's `leadAssist` move. An adapter whose platform lists the lead
4075
4083
  among their own members filters the signed-in identity out before publishing. **Being a lead is a
4076
4084
  role the provider knows, never inferred from who is listed:** it is declared at sign-in, a lead
@@ -4089,7 +4097,7 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
4089
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. |
4090
4098
  | `{ type: "release", memberId: UserId }` | Ends an imposed break on that member, whoever placed it. |
4091
4099
 
4092
- `memberId` is this provider's own identifier for the member, as published on its roster. It is
4100
+ `memberId` is this provider's own identifier for the member, as published on its team member list. It is
4093
4101
  never an identifier from another provider, and Omni does not translate between them; names come
4094
4102
  from `describeUsers()`.
4095
4103
 
@@ -4105,7 +4113,7 @@ never expressed here.
4105
4113
 
4106
4114
  An agent on a call may ask a lead to join it -- a dispute that needs approval, a customer who
4107
4115
  asks for a manager, a moment the agent wants a second pair of ears. A call centre calls this
4108
- assistance or escalation, and the name says who assists: the capability is `leadAssist` on the task; the lead's side is the roster, which is already the lead's view of the
4116
+ assistance or escalation, and the name says who assists: the capability is `leadAssist` on the task; the lead's side is the team member list, which is already the lead's view of the
4109
4117
  team, and a second lead method beside `executeTeamBreak`:
4110
4118
 
4111
4119
  ```ts
@@ -4120,7 +4128,7 @@ Required when the login declares `capabilities.team.leadAssistControl`, and gate
4120
4128
  execute({ taskId: "call-42", command: { type: "lead-assist", action: "request", note: "Refund dispute, needs approval" } })
4121
4129
  // task.leadAssist = { stage: "requested", note: "Refund dispute, needs approval", since }
4122
4130
 
4123
- // 2. Every lead entitled to it sees the request on their roster.
4131
+ // 2. Every lead entitled to it sees the request on their team member list.
4124
4132
  // team-updated: requests: [{ id: "req-7", memberId: "A-1", taskId: "call-42", allocationId: "alloc-42", note, since }]
4125
4133
 
4126
4134
  // 3. A lead joins, or declines.
@@ -4147,7 +4155,7 @@ finish is not given more -- and a provider answers a `join` from a lead on such
4147
4155
  The request stands for another lead, as it does when this one is already on a call.
4148
4156
 
4149
4157
  **On `decline`, or a request the agent withdraws with `{ type: "lead-assist", action: "cancel" }`, the
4150
- provider clears `leadAssist` from the agent's task** and drops the request from every roster. Nothing
4158
+ provider clears `leadAssist` from the agent's task** and drops the request from every team member list. Nothing
4151
4159
  else changes; the agent is still on the call.
4152
4160
 
4153
4161
  The lead then has two commands on their copy, gated by `assisting` being present, and a third
@@ -4173,7 +4181,7 @@ const leadAssistCapable = {
4173
4181
  ```
4174
4182
 
4175
4183
  Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
4176
- `describeUsers()`; names never travel on a task or a roster.
4184
+ `describeUsers()`; names never travel on a task or a team member list.
4177
4185
 
4178
4186
  ### Monitoring a call
4179
4187
 
@@ -4196,7 +4204,7 @@ declaration requires `executeTeamMonitor`, gated exactly as `executeTeamLeadAssi
4196
4204
  `team.leadAssistControl`.
4197
4205
 
4198
4206
  ```ts
4199
- // 1. The lead picks a member from the roster and starts silent.
4207
+ // 1. The lead picks a member from the team member list and starts silent.
4200
4208
  executeTeamMonitor({ command: { type: "monitor", memberId: "A-1" } })
4201
4209
 
4202
4210
  // 2. The lead's own task arrives -- task-offered with `automatic`, as a joined call does -- and
@@ -4264,7 +4272,7 @@ Omni offers Approve and Deny only while a member is `awaiting-decision`, shows `
4264
4272
  but not started, and shows `starting-after-task` as settled.
4265
4273
 
4266
4274
  **Live status, not a record.** The provider derives it from what is true now — not stored, not
4267
- historical, carrying no decision made earlier. Like the roster it belongs to, it is published
4275
+ historical, carrying no decision made earlier. Like the team member list it belongs to, it is published
4268
4276
  whole and replaced whole, and a provider that cannot say omits it.
4269
4277
 
4270
4278
  **An agent is not waiting on one person.** Authority is held by several, everyone who holds it
@@ -4829,8 +4837,8 @@ direction — the provider asking Omni to reconcile — and neither replaces the
4829
4837
 
4830
4838
  Carries a complete `Snapshot` after reconnect or when the provider explicitly requests
4831
4839
  reconciliation. `reason` is `reconnected` or `provider-requested`. Omni replaces the provider's
4832
- current status, break state, tasks, contacts, scheduled activities and team roster with this
4833
- snapshot. It carries what the login's capabilities call for — a roster for a lead, on every
4840
+ current status, break state, tasks, contacts, scheduled activities and team member list with this
4841
+ snapshot. It carries what the login's capabilities call for — a team member list for a lead, on every
4834
4842
  snapshot — and nothing they do not; a capability is withdrawn by a republished `authenticated`,
4835
4843
  never by an omission from a snapshot.
4836
4844
 
@@ -5066,9 +5074,9 @@ each connected provider.
5066
5074
 
5067
5075
  ### `team-updated`
5068
5076
 
5069
- Replaces this provider's complete `TeamRoster`. It is emitted only for an agent the provider
5070
- publishes a roster to, and it carries the whole team every time — never a change to it, for the
5071
- reason set out under **Team leads**. A lead's snapshot always carries the roster; it goes only when
5077
+ Replaces this provider's complete `TeamMembers`. It is emitted only for an agent the provider
5078
+ publishes a team member list to, and it carries the whole team every time — never a change to it, for the
5079
+ reason set out under **Team leads**. A lead's snapshot always carries the team member list; it goes only when
5072
5080
  a republished `authenticated` no longer declares `capabilities.team`.
5073
5081
 
5074
5082
  ### `contacts-updated`
@@ -5094,7 +5102,7 @@ The same treatment for a `UserId`, and needed for the same reason: user identifi
5094
5102
  issued by each provider independently, so two providers will eventually issue the same string for
5095
5103
  different people. Encode and join before storing or comparing.
5096
5104
 
5097
- Use it for every `UserId` — `interactionHistory[].by`, roster members, `memberId` on a
5105
+ Use it for every `UserId` — `interactionHistory[].by`, team members, `memberId` on a
5098
5106
  lead command, `ImposedBreak.by`. A bare one is only ever compared against another from the **same** provider; anything
5099
5107
  wider goes through this key.
5100
5108
 
@@ -5114,7 +5122,7 @@ same exported checks are used by Omni and adapter tests so their interpretations
5114
5122
  | --- | --- |
5115
5123
  | `validateManifest(manifest)` | Identity, protocol-version interoperability, authentication methods, and idle-capability shapes. |
5116
5124
  | `validateTask(task, { channel, locked? })` | Identity, channel agreement, phase, wrap allowance, capability shapes, custom controls, and browsers. Given `locked`, the values the queue locked, a task whose party stands locked carries none of them anywhere else (`task.locked.leak`). |
5117
- | `validateSnapshot(snapshot, manifest)` | Status, break state, break reasons, team roster, the stated `taskCount` reconciled against the tasks carried, and every task, contact, and activity, including idle-capability gating both ways: a contribution the manifest never declared is refused, and one it declares is required, `[]` included. |
5125
+ | `validateSnapshot(snapshot, manifest)` | Status, break state, break reasons, team member list, the stated `taskCount` reconciled against the tasks carried, and every task, contact, and activity, including idle-capability gating both ways: a contribution the manifest never declared is refused, and one it declares is required, `[]` included. |
5118
5126
  | `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
5119
5127
  | `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
5120
5128
  | `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
@@ -5137,13 +5145,13 @@ Each returns `ProtocolViolation[]` rather than throwing, so a caller can report
5137
5145
  once. A violation carries a stable `rule` id such as `task.browser.url.scheme`, the `path` it was
5138
5146
  found at such as `snapshot.tasks[0].browsers[1].url`, and a `message`.
5139
5147
 
5140
- Some rules need to know who is reading. `validateTeamRoster`, `validateSnapshot`, and
5148
+ Some rules need to know who is reading. `validateTeamMembers`, `validateSnapshot`, and
5141
5149
  `validateEventEnvelope` take an optional final `{ self, capabilities }` — the signed-in agent's
5142
- `AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a roster that
5150
+ `AuthenticationState.identity.id` and their login's `capabilities`. Given `self`, a team member list that
5143
5151
  carries that agent reports `team.member.self` or `team.request.self`. Given `capabilities`, a lead's
5144
- snapshot without a roster reports `team.required`, a roster published to a login that does not lead
5145
- reports `team.unentitled`, `requests` on a roster whose login lacks `team.leadAssistControl` reports
5146
- `team.requests.capability`, and a roster without them on a login that declares it reports
5152
+ snapshot without a team member list reports `team.required`, a team member list published to a login that does not lead
5153
+ reports `team.unentitled`, `requests` on a team member list whose login lacks `team.leadAssistControl` reports
5154
+ `team.requests.capability`, and a team member list without them on a login that declares it reports
5147
5155
  `team.requests.required`. Without them those rules are not checked, because they cannot be.
5148
5156
  `exerciseAdapter` always passes both.
5149
5157
 
@@ -5206,7 +5214,7 @@ A capability granted by a later login requires its methods just as one declared
5206
5214
 
5207
5215
  `result.notExercised` lists what the run never reached — one subject per family of rules: each
5208
5216
  optional part of a task (`task.browsers`, `task.interactionHistory`, `task.leadAssist`, …), the break's
5209
- `reasons` and `imposed`, the roster's `members` and `requests`, each declared contribution, and
5217
+ `reasons` and `imposed`, the team member list's `members` and `requests`, each declared contribution, and
5210
5218
  each event type (`event.task-ended`, …) — and so what a clean `violations` says nothing about.
5211
5219
  Nothing there is a violation: an adapter with no team has nothing to exercise. But a fixture with
5212
5220
  no tasks exercises no task rule, and a pass over it reads as coverage it is not.
@@ -5291,7 +5299,7 @@ expect(driven.violations).toEqual([]);
5291
5299
  assertReached(driven, ["task.onCall", "task.media", "event.task-ended"]);
5292
5300
  ```
5293
5301
  `assertReached(result, subjects)` is the paired assertion: it throws naming every subject the run
5294
- never met, so a test that meant to check a roster cannot pass on a fixture that never produced one.
5302
+ never met, so a test that meant to check a team member list cannot pass on a fixture that never produced one.
5295
5303
  It reads like a guarantee and is a claim the adopter keeps making: it catches an adapter that
5296
5304
  stopped reaching a subject, not a list that stopped asking, so a list can rot to nothing and stay
5297
5305
  green. Keep it honest with a control beside it -- one subject the run genuinely cannot reach,
@@ -5316,7 +5324,7 @@ cannot be established from TypeScript structure alone.
5316
5324
 
5317
5325
  | Helper | Contract checked |
5318
5326
  | --- | --- |
5319
- | `assertCapabilityWithdrawal(states, snapshot, manifest)` | A capability withdrawn by a later `authenticated` state is gone from the next snapshot: no roster for a login that no longer leads, no requests for one that may no longer join. Every state is validated on the way, `refreshing` must carry the login over, and the sequence passes only through usable states. |
5327
+ | `assertCapabilityWithdrawal(states, snapshot, manifest)` | A capability withdrawn by a later `authenticated` state is gone from the next snapshot: no team member list for a login that no longer leads, no requests for one that may no longer join. Every state is validated on the way, `refreshing` must carry the login over, and the sequence passes only through usable states. |
5320
5328
  | `assertTaskCapabilityWithdrawal(tasks, manifest, command)` | A capability withdrawn by a republish of the task is gone from the task: every task in the sequence is validated, all carry the offer's id, at least one capability the offer declared is absent at the end (a locked control is present, not withdrawn), and `command` is clean against the first task and refused against the last for want of a withdrawn capability and nothing else. Pair it with `assertCommandRefusedAfterWithdrawal` on the provider's answer. |
5321
5329
  | `assertCommandRefusedAfterWithdrawal(result)` | A command that arrives after its capability was withdrawn fails with `omni.capability-not-enabled`, named by the provider. The same assertion serves a command the provider never supported under a capability it declares. |
5322
5330
  | `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `exerciseAdapter` result. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.85",
3
+ "version": "0.1.86",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",