@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/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 = {
@@ -527,12 +527,12 @@ type DialResult =
527
527
  ### Task capabilities
528
528
 
529
529
  ```ts
530
- type DispositionCode = { id: string; label: string; group?: string };
530
+ type OutcomeCode = { id: string; label: string; group?: string };
531
531
 
532
- type DispositionRules = {
532
+ type OutcomeRules = {
533
533
  required?: boolean;
534
534
  notes?: "required" | "optional" | "none";
535
- codes?: DispositionCode[];
535
+ codes?: OutcomeCode[];
536
536
  };
537
537
 
538
538
  type Destination = {
@@ -557,7 +557,7 @@ type CustomCapability = {
557
557
 
558
558
  type SharedTaskCapabilities = {
559
559
  browsers?: true;
560
- dispositions?: true | DispositionRules;
560
+ outcomes?: true | OutcomeRules;
561
561
  custom?: CustomCapability[];
562
562
  };
563
563
 
@@ -793,7 +793,7 @@ const TASK_COMMAND_NAMES = {
793
793
  type TaskCommandName<C extends keyof typeof TASK_COMMAND_NAMES> =
794
794
  (typeof TASK_COMMAND_NAMES)[C][number];
795
795
 
796
- type DispositionPayload = { disposition?: string; notes?: string };
796
+ type OutcomePayload = { outcome?: string; notes?: string };
797
797
 
798
798
  type VoiceTaskCommand =
799
799
  | { type: "answer" }
@@ -815,19 +815,19 @@ type VoiceTaskCommand =
815
815
  | { type: "conference"; action: "remove"; destinationId: string; party?: never }
816
816
  | { type: "conference"; action: "remove"; party: true; destinationId?: never }
817
817
  | (RecordingCommand & { source: "provider" })
818
- | ({ type: "complete" } & DispositionPayload);
818
+ | ({ type: "complete" } & OutcomePayload);
819
819
 
820
820
  type ChatTaskCommand =
821
821
  | { type: "accept" }
822
822
  | { type: "decline" }
823
823
  | { type: "pause" }
824
824
  | { type: "resume" }
825
- | ({ type: "complete" } & DispositionPayload);
825
+ | ({ type: "complete" } & OutcomePayload);
826
826
 
827
827
  type EmailTaskCommand =
828
828
  | { type: "accept" }
829
829
  | { type: "decline" }
830
- | ({ type: "complete" } & DispositionPayload);
830
+ | ({ type: "complete" } & OutcomePayload);
831
831
 
832
832
  type CustomTaskCommand = { type: "custom"; name: string; [key: string]: unknown };
833
833
 
@@ -898,14 +898,14 @@ The adapter is the one that knows the value, so it is the one held to it: given
898
898
  queue locked, the validator refuses any other field carrying one, digits compared as digits so no
899
899
  formatting hides them (`task.locked.leak`), and a conformance run whose tasks lock a party's number
900
900
  or email states those values in `lockedValues`, since a run that cannot ask the question is not a
901
- pass. A host never has the value and never asks. A name is not locked. What the queue provides rather than permits — browsers, dispositions, custom
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
 
@@ -1499,7 +1499,7 @@ normative definition; matching names alone do not establish shared meaning.
1499
1499
 
1500
1500
  ### 10. Order on the wire is display order
1501
1501
 
1502
- A list the provider publishes — a destination directory, disposition codes, a task's custom
1502
+ A list the provider publishes — a destination directory, outcome codes, a task's custom
1503
1503
  controls, its browsers — is in the order the provider wants it shown, and a host keeps that order.
1504
1504
  A reader will find meaning in position unless something says there is none, so the provider
1505
1505
  decides: where it has an intention, an administrator's sequence, it lists in that order; where it
@@ -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
@@ -2368,7 +2368,7 @@ into a new session.
2368
2368
  `task-offered` introduces a new task and does not imply acceptance.
2369
2369
 
2370
2370
  `Task<C>` is channel-discriminated. For example, `Task<"email">` accepts
2371
- `browsers` and `dispositions`, but rejects voice-only controls such as `hold` and `endCall` at compile
2371
+ `browsers` and `outcomes`, but rejects voice-only controls such as `hold` and `endCall` at compile
2372
2372
  time. Runtime conformance checks also require the task channel to match its provider manifest.
2373
2373
 
2374
2374
  | Field | Contract |
@@ -2493,7 +2493,7 @@ ring-back is audio the agent hears -- so `task-media-started` arrives on the `pr
2493
2493
  its party ringing. On `answered` the task is `in-progress`; on any other outcome the media ends,
2494
2494
  `task-media-ended` starts the wrap clock as on every voice task, and the task goes to
2495
2495
  `completing`, so the agent records the
2496
- no-answer as the disposition it is -- in a campaign that is the commonest outcome there is, and it
2496
+ no-answer as the outcome it is -- in a campaign that is the commonest outcome there is, and it
2497
2497
  is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
2498
2498
  (`task.preview.dialOutcomes.required`).
2499
2499
 
@@ -2631,7 +2631,7 @@ const emailCompletion = {
2631
2631
  ```
2632
2632
 
2633
2633
  In this example, the agent has two minutes after sending the email to add notes, select a
2634
- disposition, and complete the task.
2634
+ outcome, and complete the task.
2635
2635
 
2636
2636
  `0` means completion may happen immediately. With `provider-automatic`, the provider may complete
2637
2637
  without waiting for a command; with `agent-command`, it still waits for `complete`.
@@ -2694,7 +2694,7 @@ window it cannot, and declaring it there changes nothing.
2694
2694
  ```ts
2695
2695
  const connectBackCapable = {
2696
2696
  channel: "voice",
2697
- capabilities: { hold: true, connectBack: true, dispositions: true },
2697
+ capabilities: { hold: true, connectBack: true, outcomes: true },
2698
2698
  phase: "completing",
2699
2699
  completionMode: "provider-automatic",
2700
2700
  wrapAllowance: 30,
@@ -2737,8 +2737,7 @@ Migration from the earlier spelling:
2737
2737
  Update producers, consumers, saved task snapshots, and validation-rule assertions together.
2738
2738
  History/report rule names now use `interactionHistory`/`interactionReport`; phase and conformance
2739
2739
  rules use `interaction` and final-task rules use `completion`. Legacy fields are rejected even
2740
- when the new field is also supplied. `DispositionCode`, `DispositionRules`, `DispositionPayload`,
2741
- `dispositions`, and `disposition` still describe outcome codes and are unchanged.
2740
+ when the new field is also supplied.
2742
2741
 
2743
2742
 
2744
2743
  `Task.interactionHistory` is the call record: the steps that brought the task to the agent, oldest
@@ -2858,7 +2857,7 @@ Four rules a provider has to keep:
2858
2857
  - **`by` is a bare `UserId`, and not necessarily an agent.** A lead or a manager takes part
2859
2858
  during an interaction too — a transfer accepted, a call conferenced in — so the field names whoever it was,
2860
2859
  the same way `ImposedBreak.by` does. It comes from this provider's own directory, the same
2861
- 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
2862
2861
  within a provider and never across one.
2863
2862
  - **A task carries no names.** Omni resolves what to display with `describeUsers()`. Two people
2864
2863
  called Arun on one site is ordinary, and anything pairing entries on a display name pairs them
@@ -3079,7 +3078,7 @@ source is one more published fact about them, and the one that lets a host tell
3079
3078
 
3080
3079
  **Completing the task is not a capability, and no capability set withholds it.** `complete` is
3081
3080
  governed by `completionMode` alone: under `agent-command` it is always available, whatever the set
3082
- says, and the `dispositions` capability decides only whether a code travels with it. Likewise
3081
+ says, and the `outcomes` capability decides only whether a code travels with it. Likewise
3083
3082
  Answer on a pending task and Call on a preview are the phase's controls, not the set's. The
3084
3083
  capability set governs what the agent may do *with* the task; completing it is never on the
3085
3084
  list.
@@ -3173,7 +3172,7 @@ const taskCapabilities = {
3173
3172
  channel: "voice",
3174
3173
  capabilities: {
3175
3174
  hold: true,
3176
- dispositions: true,
3175
+ outcomes: true,
3177
3176
  },
3178
3177
  browsers: [],
3179
3178
  } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "browsers">;
@@ -3181,7 +3180,7 @@ const taskCapabilities = {
3181
3180
 
3182
3181
  A capability says the control may be offered; the task's phase says whether there is anything to use
3183
3182
  it on. Every control below that acts on the call or the conversation -- everything but `decline`,
3184
- `connectBack` and `dispositions` -- is offered only while the task is `in-progress` or `paused`.
3183
+ `connectBack` and `outcomes` -- is offered only while the task is `in-progress` or `paused`.
3185
3184
  See **Which commands need a capability**.
3186
3185
 
3187
3186
  ### Voice capabilities
@@ -3197,20 +3196,40 @@ See **Which commands need a capability**.
3197
3196
  | `leadAssist` | Secondary menu item: Lead assist | Omni may ask a lead to join this call, with a note. The lead's decision reaches the agent on `Task.leadAssist`. See **Lead assist**. |
3198
3197
  | `conference` | Secondary button: Conference | Omni may dial a destination into the active call, and remove one person from it -- a conferenced entry, one still ringing included, which calls the dial off, or the party, leaving the agent with the colleague. See **Ending a call, and removing one person from it**. |
3199
3198
  | `recording` | Overflow menu item: Recording | Per-task provider and host policies expose only their permitted recording actions; each has independent state and routing. |
3200
- | `dispositions` | Primary button: Complete | Omni may request task completion with a provider disposition and notes. |
3199
+ | `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
3201
3200
 
3202
3201
  ### Publishing codes and destinations
3203
3202
 
3204
3203
  Four capabilities accept an object when the provider wants Omni to render real choices. For
3205
- `dispositions` alone, `true` remains valid and means "offer the control with nothing published";
3204
+ `outcomes` alone, `true` remains valid and means "offer the control with nothing published";
3206
3205
  the three directory controls -- `coldTransfer`, `warmTransfer`, `conference` -- carry their
3207
3206
  directory or are refused, since once nothing is typed the directory is the control.
3208
3207
 
3209
- #### `dispositions`
3208
+ #### `outcomes`
3209
+
3210
+ An outcome describes what happened; completion finishes the task. Outcome codes are selected
3211
+ from the task's own published list. They are distinct from `DialOutcome`, which reports the
3212
+ result of a dial attempt.
3213
+
3214
+ Migration requires providers and hosts to adopt the renamed types and fields together:
3215
+
3216
+ | Former API | Current API |
3217
+ | --- | --- |
3218
+ | DispositionCode | `OutcomeCode` |
3219
+ | DispositionRules | `OutcomeRules` |
3220
+ | DispositionPayload | `OutcomePayload` |
3221
+ | Task.capabilities.dispositions | `Task.capabilities.outcomes` |
3222
+ | complete.disposition | `complete.outcome` |
3223
+
3224
+ Validation and conformance diagnostics use `task.outcomes`, `task.outcome`, and
3225
+ `command.complete.outcome` in place of the former names. Update retained task snapshots and
3226
+ command producers as well as imports. Old capability and command fields are rejected, including
3227
+ payloads containing both names; there are no compatibility aliases. Code IDs, notes, required
3228
+ selection rules, and completion behavior are unchanged.
3210
3229
 
3211
3230
  ```ts
3212
3231
  capabilities: {
3213
- dispositions: {
3232
+ outcomes: {
3214
3233
  required: true,
3215
3234
  notes: "optional",
3216
3235
  codes: [
@@ -3225,9 +3244,9 @@ capabilities: {
3225
3244
  | --- | --- |
3226
3245
  | `required` | When `true`, Omni must collect a code before issuing `complete`. A required policy must publish at least one code. |
3227
3246
  | `notes` | `required`, `optional`, or `none`; controls the free-text field beside the code. |
3228
- | `codes` | Codes Omni offers. `id` values are non-empty and unique; Omni sends the chosen `id` as `TaskCommand.complete.disposition`. |
3247
+ | `codes` | Codes Omni offers. `id` values are non-empty and unique; Omni sends the chosen `id` as `TaskCommand.complete.outcome`. |
3229
3248
 
3230
- With `dispositions: true` Omni shows a Complete control and sends `complete` with no code, because
3249
+ With `outcomes: true` Omni shows a Complete control and sends `complete` with no code, because
3231
3250
  the provider published none.
3232
3251
 
3233
3252
  #### `coldTransfer`, `warmTransfer` and `conference`
@@ -3360,14 +3379,14 @@ customer.
3360
3379
  | --- | --- | --- |
3361
3380
  | `decline` | Pending-task button: Decline | The provider can decline a pending chat offer. Omni shows it only when provisioning also permits declining. |
3362
3381
  | `hold` | Primary toggle: Hold | Omni may pause and resume the agent’s interaction in the chat. |
3363
- | `dispositions` | Primary button: Complete | Omni may request task completion with a provider disposition and notes. |
3382
+ | `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
3364
3383
 
3365
3384
  ### Email capabilities
3366
3385
 
3367
3386
  | Capability | Omni UI | Contract |
3368
3387
  | --- | --- | --- |
3369
3388
  | `decline` | Pending-task button: Decline | The provider can decline a pending email offer. Omni shows it only when provisioning also permits declining. |
3370
- | `dispositions` | Primary button: Complete | Omni may request task completion with a provider disposition and notes. |
3389
+ | `outcomes` | Primary button: Complete | Omni may request task completion with a provider outcome and notes. |
3371
3390
 
3372
3391
  ### Custom capabilities
3373
3392
 
@@ -3707,7 +3726,7 @@ Your own tasks are the only ones you count. What the agent holds at other provid
3707
3726
  concern — Omni set `count` knowing it, and this is how: the agent is one person on several
3708
3727
  providers, and the host divides their capacity among them rather than telling each the whole. A
3709
3728
  provider that has none of it for now is told **`count: 0`, host-stopped**: allocate nothing, show
3710
- 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,
3711
3730
  a fact this provider holds, where `on-task` would assert work it cannot see -- and take the next
3712
3731
  count as any other when the host has capacity for this provider again. Zero is the one restatement
3713
3732
  that follows work rather than provisioning.
@@ -3745,7 +3764,7 @@ request object only to the request method, and undefined to the other three.
3745
3764
 
3746
3765
  Use `validateTeamBreakCommand(request, context)` for lead decisions, placement, release and
3747
3766
  policy commands. It requires the live lead capability and active transport, a current target
3748
- 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.
3749
3768
  Approve/deny requires an awaiting decision; placement uses the target's reason codes; release
3750
3769
  requires an imposed committed break. The context's target state must belong to the named member;
3751
3770
  that association and backend authorization are provider responsibilities.
@@ -4007,19 +4026,27 @@ so there is nothing on it to resume.
4007
4026
  ## Team leads
4008
4027
 
4009
4028
  A lead who also takes calls sees their team on the idle dashboard. `Snapshot.team` carries a
4010
- `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
+
4011
4038
 
4012
4039
  | Field | Contract |
4013
4040
  | --- | --- |
4014
- | `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. |
4015
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**. |
4016
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**. |
4017
4044
 
4018
4045
  | `TeamMember` field | Contract |
4019
4046
  | --- | --- |
4020
- | `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()`. |
4021
4048
  | `availability` | Required. What the member is doing now. |
4022
- | `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. |
4023
4050
  | `break` | Present only while the member has an outstanding break request. See **A member waiting for a break**. |
4024
4051
 
4025
4052
  Each availability value means one thing:
@@ -4032,7 +4059,7 @@ Each availability value means one thing:
4032
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**. |
4033
4060
  | `signed-out` | Known to this team but not signed in to this provider. |
4034
4061
 
4035
- **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
4036
4063
  adapter over a best-effort channel with no ordering and no delivery guarantee, so a stream of deltas
4037
4064
  cannot be trusted to reconstruct the truth. The adapter reconciles against its own authoritative
4038
4065
  read and publishes the result.
@@ -4043,15 +4070,15 @@ everybody — worse than showing nothing, because it looks like data. Send it on
4043
4070
  knows when the state actually began. It times the current `availability`, so it moves every time
4044
4071
  that value does.
4045
4072
 
4046
- **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
4047
4074
  every snapshot, and to nobody else. Omni never decides who leads a team: the provider said so at
4048
- 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
4049
4076
  else, which is the correct rendering for an agent who leads nobody. What the lead may do with the
4050
- 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.
4051
4078
 
4052
- **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
4053
4080
  `requests`.** A lead does not report to themself: their own break request and their own ask for a
4054
- 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
4055
4082
  only their own `BreakState` and their task's `leadAssist` move. An adapter whose platform lists the lead
4056
4083
  among their own members filters the signed-in identity out before publishing. **Being a lead is a
4057
4084
  role the provider knows, never inferred from who is listed:** it is declared at sign-in, a lead
@@ -4070,7 +4097,7 @@ One method, `executeTeamBreak`, taking a discriminated command exactly as `execu
4070
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. |
4071
4098
  | `{ type: "release", memberId: UserId }` | Ends an imposed break on that member, whoever placed it. |
4072
4099
 
4073
- `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
4074
4101
  never an identifier from another provider, and Omni does not translate between them; names come
4075
4102
  from `describeUsers()`.
4076
4103
 
@@ -4086,7 +4113,7 @@ never expressed here.
4086
4113
 
4087
4114
  An agent on a call may ask a lead to join it -- a dispute that needs approval, a customer who
4088
4115
  asks for a manager, a moment the agent wants a second pair of ears. A call centre calls this
4089
- 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
4090
4117
  team, and a second lead method beside `executeTeamBreak`:
4091
4118
 
4092
4119
  ```ts
@@ -4101,7 +4128,7 @@ Required when the login declares `capabilities.team.leadAssistControl`, and gate
4101
4128
  execute({ taskId: "call-42", command: { type: "lead-assist", action: "request", note: "Refund dispute, needs approval" } })
4102
4129
  // task.leadAssist = { stage: "requested", note: "Refund dispute, needs approval", since }
4103
4130
 
4104
- // 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.
4105
4132
  // team-updated: requests: [{ id: "req-7", memberId: "A-1", taskId: "call-42", allocationId: "alloc-42", note, since }]
4106
4133
 
4107
4134
  // 3. A lead joins, or declines.
@@ -4128,7 +4155,7 @@ finish is not given more -- and a provider answers a `join` from a lead on such
4128
4155
  The request stands for another lead, as it does when this one is already on a call.
4129
4156
 
4130
4157
  **On `decline`, or a request the agent withdraws with `{ type: "lead-assist", action: "cancel" }`, the
4131
- 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
4132
4159
  else changes; the agent is still on the call.
4133
4160
 
4134
4161
  The lead then has two commands on their copy, gated by `assisting` being present, and a third
@@ -4138,7 +4165,7 @@ choice that is no command at all:
4138
4165
  | --- | --- | --- |
4139
4166
  | `{ type: "lead-assist", action: "take-over" }` | `task-media-ended`, then `task-ended` with `{ type: "taken-over", leadId }`: **no `completing` window**, the agent is idle at once. The audio ends first, as before every voice ending (`stream.taskEnded.mediaOpen`), and the lead is named by user id, since a lead is not a directory item | Continues alone, and ends as any call does |
4140
4167
  | `{ type: "lead-assist", action: "leave" }` | Continues; `leadAssist` is cleared | `task-media-ended`, then `task-ended` with `{ type: "left" }` -- the call goes on without them |
4141
- | Stays until the customer hangs up | `task-media-ended`, `completing`, its own disposition | The same, independently: **both have the completion window** |
4168
+ | Stays until the customer hangs up | `task-media-ended`, `completing`, its own outcome | The same, independently: **both have the completion window** |
4142
4169
 
4143
4170
  `left` is the one outcome that ends a task without ending the call: this agent left a call that
4144
4171
  continues without them. It reads as neither a completion nor a cancellation, because it is
@@ -4147,14 +4174,14 @@ neither.
4147
4174
  ```ts
4148
4175
  const leadAssistCapable = {
4149
4176
  channel: "voice",
4150
- capabilities: { hold: true, leadAssist: true, dispositions: true },
4177
+ capabilities: { hold: true, leadAssist: true, outcomes: true },
4151
4178
  phase: "in-progress",
4152
4179
  leadAssist: { stage: "joined", leadId: "L-9", note: "Refund dispute, needs approval", since: "2026-08-21T09:04:00Z" },
4153
4180
  } satisfies Pick<Task<"voice">, "channel" | "capabilities" | "phase" | "leadAssist">;
4154
4181
  ```
4155
4182
 
4156
4183
  Lead and member alike are `UserId`s of this provider, so an adapter publishing them implements
4157
- `describeUsers()`; names never travel on a task or a roster.
4184
+ `describeUsers()`; names never travel on a task or a team member list.
4158
4185
 
4159
4186
  ### Monitoring a call
4160
4187
 
@@ -4177,7 +4204,7 @@ declaration requires `executeTeamMonitor`, gated exactly as `executeTeamLeadAssi
4177
4204
  `team.leadAssistControl`.
4178
4205
 
4179
4206
  ```ts
4180
- // 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.
4181
4208
  executeTeamMonitor({ command: { type: "monitor", memberId: "A-1" } })
4182
4209
 
4183
4210
  // 2. The lead's own task arrives -- task-offered with `automatic`, as a joined call does -- and
@@ -4245,7 +4272,7 @@ Omni offers Approve and Deny only while a member is `awaiting-decision`, shows `
4245
4272
  but not started, and shows `starting-after-task` as settled.
4246
4273
 
4247
4274
  **Live status, not a record.** The provider derives it from what is true now — not stored, not
4248
- 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
4249
4276
  whole and replaced whole, and a provider that cannot say omits it.
4250
4277
 
4251
4278
  **An agent is not waiting on one person.** Authority is held by several, everyone who holds it
@@ -4468,8 +4495,8 @@ with a stale view: a flip against a state the provider has already changed turns
4468
4495
  then off again. A custom `toggle` control therefore carries its own boolean. `hold` and `resume`,
4469
4496
  `pause` and `resume` need no flag, being pairs rather than toggles.
4470
4497
 
4471
- **`complete` sends a disposition only where one was published.** `disposition` is a
4472
- `DispositionCode.id` from the task's own `dispositions` capability, and `notes` obeys that
4498
+ **`complete` sends an outcome only where one was published.** `outcome` is a
4499
+ `OutcomeCode.id` from the task's own `outcomes` capability, and `notes` obeys that
4473
4500
  capability's `notes` setting. A task publishing no codes still receives `complete`, with neither.
4474
4501
 
4475
4502
  ### Where a command executes
@@ -4574,7 +4601,7 @@ declared:
4574
4601
  | `conference` with `action: "remove"` | The `conference` capability, and somebody else on the call: a remove that would leave the agent alone is `end-call`, and a provider answers it `failed`. |
4575
4602
  | `decline` | The `decline` capability on any channel, **and** Omni provisioning permitting it. One word for refusing an offer, whatever the channel. |
4576
4603
  | `call` | The `preview` phase. A record put in front of an agent is there to be called, so the phase is the gate and there is no capability. It is a dial, with a `dialId` and a `dial-outcome`. |
4577
- | `complete` | `completionMode: "agent-command"`. The `dispositions` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. What travels is what the capability published: a code from its list where it has one (`command.complete.disposition.unknown`), a code at all where it requires one (`.disposition.required`), notes as it said (`.notes.required`, `.notes.unexpected`), and neither where the task declares no dispositions (`.disposition.unexpected`). |
4604
+ | `complete` | `completionMode: "agent-command"`. The `outcomes` capability decides whether a code travels with the command, never whether the command exists — a task Omni cannot complete never ends. What travels is what the capability published: a code from its list where it has one (`command.complete.outcome.unknown`), a code at all where it requires one (`.outcome.required`), notes as it said (`.notes.required`, `.notes.unexpected`), and neither where the task declares no outcomes (`.outcome.unexpected`). |
4578
4605
  | `connect-back` | The `connectBack` capability **and** the `completing` phase. It exists to reach the party again after the call, so it has no meaning while the call is up. |
4579
4606
  | `transfer` with `action: "warm"` | The `warmTransfer` capability. `action: "cold"` is gated by `coldTransfer`; the two are declared and offered separately. |
4580
4607
  | `transfer` with `action: "complete"` or `"cancel"` | A consultation in progress -- a `consulted` entry on `Task.onCall`. Without one there is nothing to complete or cancel, and a provider that receives either answers `failed`. |
@@ -4810,8 +4837,8 @@ direction — the provider asking Omni to reconcile — and neither replaces the
4810
4837
 
4811
4838
  Carries a complete `Snapshot` after reconnect or when the provider explicitly requests
4812
4839
  reconciliation. `reason` is `reconnected` or `provider-requested`. Omni replaces the provider's
4813
- current status, break state, tasks, contacts, scheduled activities and team roster with this
4814
- 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
4815
4842
  snapshot — and nothing they do not; a capability is withdrawn by a republished `authenticated`,
4816
4843
  never by an omission from a snapshot.
4817
4844
 
@@ -5047,9 +5074,9 @@ each connected provider.
5047
5074
 
5048
5075
  ### `team-updated`
5049
5076
 
5050
- Replaces this provider's complete `TeamRoster`. It is emitted only for an agent the provider
5051
- publishes a roster to, and it carries the whole team every time — never a change to it, for the
5052
- 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
5053
5080
  a republished `authenticated` no longer declares `capabilities.team`.
5054
5081
 
5055
5082
  ### `contacts-updated`
@@ -5075,7 +5102,7 @@ The same treatment for a `UserId`, and needed for the same reason: user identifi
5075
5102
  issued by each provider independently, so two providers will eventually issue the same string for
5076
5103
  different people. Encode and join before storing or comparing.
5077
5104
 
5078
- 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
5079
5106
  lead command, `ImposedBreak.by`. A bare one is only ever compared against another from the **same** provider; anything
5080
5107
  wider goes through this key.
5081
5108
 
@@ -5095,7 +5122,7 @@ same exported checks are used by Omni and adapter tests so their interpretations
5095
5122
  | --- | --- |
5096
5123
  | `validateManifest(manifest)` | Identity, protocol-version interoperability, authentication methods, and idle-capability shapes. |
5097
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`). |
5098
- | `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. |
5099
5126
  | `validateEventEnvelope(envelope, manifest)` | Envelope identity, timestamp, and the payload for each event type. |
5100
5127
  | `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
5101
5128
  | `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
@@ -5118,13 +5145,13 @@ Each returns `ProtocolViolation[]` rather than throwing, so a caller can report
5118
5145
  once. A violation carries a stable `rule` id such as `task.browser.url.scheme`, the `path` it was
5119
5146
  found at such as `snapshot.tasks[0].browsers[1].url`, and a `message`.
5120
5147
 
5121
- Some rules need to know who is reading. `validateTeamRoster`, `validateSnapshot`, and
5148
+ Some rules need to know who is reading. `validateTeamMembers`, `validateSnapshot`, and
5122
5149
  `validateEventEnvelope` take an optional final `{ self, capabilities }` — the signed-in agent's
5123
- `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
5124
5151
  carries that agent reports `team.member.self` or `team.request.self`. Given `capabilities`, a lead's
5125
- snapshot without a roster reports `team.required`, a roster published to a login that does not lead
5126
- reports `team.unentitled`, `requests` on a roster whose login lacks `team.leadAssistControl` reports
5127
- `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
5128
5155
  `team.requests.required`. Without them those rules are not checked, because they cannot be.
5129
5156
  `exerciseAdapter` always passes both.
5130
5157
 
@@ -5187,7 +5214,7 @@ A capability granted by a later login requires its methods just as one declared
5187
5214
 
5188
5215
  `result.notExercised` lists what the run never reached — one subject per family of rules: each
5189
5216
  optional part of a task (`task.browsers`, `task.interactionHistory`, `task.leadAssist`, …), the break's
5190
- `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
5191
5218
  each event type (`event.task-ended`, …) — and so what a clean `violations` says nothing about.
5192
5219
  Nothing there is a violation: an adapter with no team has nothing to exercise. But a fixture with
5193
5220
  no tasks exercises no task rule, and a pass over it reads as coverage it is not.
@@ -5198,7 +5225,7 @@ outcome -- stays in `notExercised` for every adapter, and a rule about a live ca
5198
5225
  in each adopter's own tests. `{ drive: true }` closes that: the exercise takes the first task the
5199
5226
  provider offers through one ordinary lifecycle -- accept it, wait for its media and open it on a
5200
5227
  softphone, hold and resume where the task offers `hold`, end the call where it offers `endCall`,
5201
- complete it with a disposition where the agent completes -- and holds every step to the rules a
5228
+ complete it with an outcome where the agent completes -- and holds every step to the rules a
5202
5229
  host holds a provider to. Each command is validated against the task as published
5203
5230
  (`drive.command.*`), each answer for its method, a refusal of a control the task offered is a
5204
5231
  violation (`drive.command.failed`), and an event the provider owes and never sends is one too
@@ -5272,7 +5299,7 @@ expect(driven.violations).toEqual([]);
5272
5299
  assertReached(driven, ["task.onCall", "task.media", "event.task-ended"]);
5273
5300
  ```
5274
5301
  `assertReached(result, subjects)` is the paired assertion: it throws naming every subject the run
5275
- 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.
5276
5303
  It reads like a guarantee and is a claim the adopter keeps making: it catches an adapter that
5277
5304
  stopped reaching a subject, not a list that stopped asking, so a list can rot to nothing and stay
5278
5305
  green. Keep it honest with a control beside it -- one subject the run genuinely cannot reach,
@@ -5297,7 +5324,7 @@ cannot be established from TypeScript structure alone.
5297
5324
 
5298
5325
  | Helper | Contract checked |
5299
5326
  | --- | --- |
5300
- | `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. |
5301
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. |
5302
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. |
5303
5330
  | `assertReached(result, subjects)` | The exercise met every subject named; throws listing those it did not. Pair it with a clean `exerciseAdapter` result. |
@@ -5405,7 +5432,7 @@ same operation; reuse with different contents is refused. It never silently retr
5405
5432
  operation. Implementations without reliable request correlation must refuse controls.
5406
5433
 
5407
5434
  Keep command progress in host UI separately from authoritative state. Applied means the requested
5408
- transition and disposition actually completed; publish the confirming task or host report before
5435
+ recording transition and any retain/discard effect actually completed; publish the confirming task or host report before
5409
5436
  resolving applied. Failed means confirmed no effect. Rejected promise means outcome unknown:
5410
5437
  show the failure, reconcile that path and do not automatically retry or pretend inactive. Authoritative
5411
5438
  updates remain full current task/host views, not replayed provider events. These current-state fields