@xema/omni-protocol 0.1.82 → 0.1.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +54 -8
- package/dist/testing.d.ts +9 -6
- package/dist/testing.js +60 -53
- package/dist/validation.d.ts +33 -0
- package/dist/validation.js +319 -46
- package/guide.md +391 -79
- package/package.json +1 -1
package/guide.md
CHANGED
|
@@ -189,6 +189,8 @@ type Manifest<C extends Channel = Channel> = {
|
|
|
189
189
|
supportedProtocolVersions: number[];
|
|
190
190
|
authenticationMethods: AuthenticationMethod[];
|
|
191
191
|
idleCapabilities?: IdleCapabilities<C>;
|
|
192
|
+
timeCheck?: true;
|
|
193
|
+
timestampAuthority?: "provider";
|
|
192
194
|
phaseLabels?: TaskPhaseLabels;
|
|
193
195
|
taskTypePresentation?: Record<string, TaskTypePresentation>;
|
|
194
196
|
orgLevels?: LevelDeclaration[];
|
|
@@ -350,7 +352,15 @@ type HostGuarantees = {
|
|
|
350
352
|
personConsent?: true;
|
|
351
353
|
};
|
|
352
354
|
|
|
355
|
+
type ProviderTimeScope = { providerId: string; loginId: string };
|
|
356
|
+
type ProviderTimeEstimate = ProviderTimeScope & {
|
|
357
|
+
at: IsoTimestamp;
|
|
358
|
+
clockId: string;
|
|
359
|
+
uncertaintyMs?: number;
|
|
360
|
+
};
|
|
361
|
+
|
|
353
362
|
type Host = {
|
|
363
|
+
estimateProviderTime?(scope: ProviderTimeScope): ProviderTimeEstimate | undefined;
|
|
354
364
|
recording?: HostRecording;
|
|
355
365
|
guarantees: HostGuarantees;
|
|
356
366
|
mute?: HostMute;
|
|
@@ -458,7 +468,7 @@ type HandlingReport = { taskId: TaskId; allocationId: AllocationId; at: IsoTimes
|
|
|
458
468
|
);
|
|
459
469
|
|
|
460
470
|
type HandlingReportResult =
|
|
461
|
-
| { status: "recorded" }
|
|
471
|
+
| { status: "recorded"; at: IsoTimestamp }
|
|
462
472
|
| { status: "failed"; failure: ProtocolFailure };
|
|
463
473
|
|
|
464
474
|
type PreferenceResult =
|
|
@@ -739,7 +749,7 @@ type Task<C extends Channel = Channel> = {
|
|
|
739
749
|
: { recording?: never; onCall?: never; leadAssist?: never; assisting?: never; monitoring?: never; media?: never }
|
|
740
750
|
);
|
|
741
751
|
|
|
742
|
-
type PreviewDeadline = "calls" | "
|
|
752
|
+
type PreviewDeadline = "calls" | "host-calls" | "waits";
|
|
743
753
|
|
|
744
754
|
type AcceptanceMode =
|
|
745
755
|
| "no-preference"
|
|
@@ -1128,7 +1138,22 @@ type Refusal = {
|
|
|
1128
1138
|
violations: ProtocolViolation[];
|
|
1129
1139
|
};
|
|
1130
1140
|
|
|
1141
|
+
type ProviderTimeCheckPolicy = {
|
|
1142
|
+
intervalMs: number;
|
|
1143
|
+
timeoutMs: number;
|
|
1144
|
+
maxRoundTripMs: number;
|
|
1145
|
+
maxSampleAgeMs: number;
|
|
1146
|
+
};
|
|
1147
|
+
type ProviderTimeCheckRequest = { requestId: string };
|
|
1148
|
+
type ProviderTimeCheckResult = {
|
|
1149
|
+
requestId: string;
|
|
1150
|
+
loginId: string;
|
|
1151
|
+
clockId: string;
|
|
1152
|
+
providerTime: IsoTimestamp;
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1131
1155
|
type Connection<C extends Channel = Channel> = {
|
|
1156
|
+
checkTime?(request: ProviderTimeCheckRequest): Promise<ProviderTimeCheckResult>;
|
|
1132
1157
|
snapshot(): Snapshot<C> | Promise<Snapshot<C>>;
|
|
1133
1158
|
subscribe(listener: (envelope: ProviderEventEnvelope<C>) => void): Unsubscribe;
|
|
1134
1159
|
refused(report: Refusal): void;
|
|
@@ -1583,6 +1608,8 @@ compile time.
|
|
|
1583
1608
|
| `orgLevels` | The organisation's whole ladder as the provider calls it, each level with the label a desk shows for "who decided". Stated outright, `person` included: what it leaves out does not exist. Omitted for the typical four, `DEFAULT_LEVELS`. See **Who decides what an agent may do**. |
|
|
1584
1609
|
| `phones` | Voice only, and required there: the phones this platform can put an agent on, `softphone` (the call's audio lands in the host) and/or `deskPhone` (a handset the platform rings; the host shows the call and opens nothing). The host picks one per login. See **How the agent hears the call**. |
|
|
1585
1610
|
| `dialOutcomes` | Voice only. How a dial can end on this platform, as it distinguishes them: `answered` and at least one way of not reaching the destination. Required of a provider that dials at all — an idle dialpad, or tasks that transfer, conference or call back — and a `dial-outcome` carries only a declared member. See **Every dial has an outcome**. |
|
|
1611
|
+
| `timeCheck` | Optional `true`: implements `checkTime` for fresh provider-clock samples; host polling is independently opt-in. |
|
|
1612
|
+
| `timestampAuthority` | Optional `"provider"`: provider timestamps are final; host instants are advisory. Omission makes no trust promise. |
|
|
1586
1613
|
| `runningStepReports` | The provider takes running reports of a host-performed step — `recordStep` with `seconds` so far and no `ended`. Omitted, the host sends exactly two reports per leg, when it began and when it ended, and a running one is refused. See **The host records what it performs**. |
|
|
1587
1614
|
| `disposalSettleMs` | Required. How long after an applied disposal -- `complete`, or a lead's `take-over` -- the provider's `task-ended` is owed, a positive whole number of milliseconds (`manifest.disposalSettleMs`). A warm transfer's `complete` is not a disposal: the agent's wrap runs after it. Stated per provider, since platforms settle at different speeds. See **`task-ended`**. |
|
|
1588
1615
|
|
|
@@ -2118,6 +2145,7 @@ surface in one place, and what obliges an adapter to implement each one.
|
|
|
2118
2145
|
| Method | Implement it when |
|
|
2119
2146
|
| --- | --- |
|
|
2120
2147
|
| `snapshot()` | Always. |
|
|
2148
|
+
| `checkTime(request)` | The manifest declares `timeCheck: true`. Read-only clock check; no task command or accuracy guarantee. |
|
|
2121
2149
|
| `subscribe(listener)` | Always. |
|
|
2122
2150
|
| `disconnect()` | Always. |
|
|
2123
2151
|
| `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**. |
|
|
@@ -2353,10 +2381,10 @@ time. Runtime conformance checks also require the task channel to match its prov
|
|
|
2353
2381
|
| `browsers` | Named browser definitions for the task workspace: at least one when the task declares the `browsers` capability, empty when it does not. |
|
|
2354
2382
|
| `party` | The person or entity on the other end of this task, as a `Contact`: often a name and one address; a withheld caller ID may leave nothing to send at all. Optional. The party is who the task is *with*; `contacts` is the directory. |
|
|
2355
2383
|
| `phase` | Current canonical task phase: `pending`, `confirmed`, `preview`, `in-progress`, `paused`, or `completing`. `preview` is voice only. |
|
|
2356
|
-
| `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. Media names a task whose work has begun, or whose party the host is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.media.beforeWork`), on a snapshot as on the event, since a host opens the microphone on it; with the party ringing by a host dial, ring-back
|
|
2384
|
+
| `media` | Voice only. The task's real-time audio as the provider holds it: `started` while audio is attached, `ended` once it ended, omitted while none is. The provider's word — see **`task-media-started`**. Media names a task whose work has begun, or whose party the host is dialling: on a `pending`, `confirmed` or `preview` task with nobody ringing it is refused (`task.media.beforeWork`), on a snapshot as on the event, since a host opens the microphone on it; with the party ringing by a host dial or provider-triggered preview dial, actual ring-back media may precede answer. |
|
|
2357
2385
|
| `acceptance` | How this offer is accepted — `no-preference`, `consent`, or `automatic` — stated on the pending task so a reconnect snapshot says it too. Required while `pending` when `autoAcceptTasks` was `true`, forbidden when it was `false`, and absent past `pending`. See **Acceptance modes**. |
|
|
2358
|
-
| `previewEndsAt` | Voice only, in `preview`:
|
|
2359
|
-
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls`
|
|
2386
|
+
| `previewEndsAt` | Voice only, in `preview`: the preparation target instant. Absent, the agent has as long as they need without a preparation countdown. Always with `atDeadline`. See **Preview: the agent presses Call**. |
|
|
2387
|
+
| `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls` makes the provider initiate dialing, `host-calls` makes the host issue Call, `waits` keeps the task in preview awaiting the agent. |
|
|
2360
2388
|
| `reference` | Optional agent-facing reference such as a case, call, conversation, ticket, or message number. It is distinct from the protocol `id`. |
|
|
2361
2389
|
| `completionMode` | `agent-command` waits for the channel's `complete` command; `provider-automatic` completes without one. |
|
|
2362
2390
|
| `wrapAllowance` | Fixed time allowed to complete the task after primary handling ends. For real-time media, it begins after `task-media-ended`. Required under `provider-automatic`, where the provider acts on it. Optional under `agent-command`: omitted says the provider imposes no deadline, and Omni counts nothing down. |
|
|
@@ -2399,7 +2427,7 @@ The canonical task transitions are:
|
|
|
2399
2427
|
| `confirmed` | Work begins | `in-progress` |
|
|
2400
2428
|
| `preview` | Agent presses Call (`call`) and the customer answers, or the deadline `calls` and they answer | `in-progress` |
|
|
2401
2429
|
| `preview` | The call goes out and nobody answers | `completing` |
|
|
2402
|
-
| `preview` | The
|
|
2430
|
+
| `preview` | The preparation target passes with `atDeadline: "waits"` | Remains `preview`, waiting for the agent to press Call |
|
|
2403
2431
|
| `pending` | Provider withdraws the allocation, the agent declines, or the party abandons the ring | Removed by `task-ended` with `cancelled` outcome, `by` saying which |
|
|
2404
2432
|
| `pending` | The offer lapses at `allocationExpiresAt` | Removed by `task-ended` with `expired` outcome naming `pending` |
|
|
2405
2433
|
| No task | Snapshot reports work already underway | `in-progress` |
|
|
@@ -2437,8 +2465,9 @@ command.
|
|
|
2437
2465
|
#### Preview: the agent presses Call
|
|
2438
2466
|
|
|
2439
2467
|
An outbound campaign that lets the agent see who they are about to call is a **preview**: the
|
|
2440
|
-
record arrives as a task
|
|
2441
|
-
out. The agent
|
|
2468
|
+
record arrives as a task and sits in `preview` with the party on it before a call goes
|
|
2469
|
+
out. The agent prepares; manual or declared automatic initiation may then begin dialing. It
|
|
2470
|
+
remains preview while ringing until an evidenced answer or non-answer outcome. The agent may press Call. That is the whole phase, and it exists on voice alone --
|
|
2442
2471
|
chat and email have no call to place, and their reading is ordinary work in `in-progress`.
|
|
2443
2472
|
|
|
2444
2473
|
```ts
|
|
@@ -2466,14 +2495,51 @@ no-answer as the disposition it is -- in a campaign that is the commonest outcom
|
|
|
2466
2495
|
is work, not a cancellation. A `preview` task therefore needs a manifest that says how a dial ends
|
|
2467
2496
|
(`task.preview.dialOutcomes.required`).
|
|
2468
2497
|
|
|
2469
|
-
**
|
|
2470
|
-
`previewEndsAt
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2498
|
+
**Preparation and trigger ownership are per task.** Unlimited preparation omits both
|
|
2499
|
+
`previewEndsAt` and `atDeadline`: only an agent pressing Call starts dialing. Fixed preparation
|
|
2500
|
+
carries the provider's authoritative preparation-end instant and exactly one trigger owner:
|
|
2501
|
+
|
|
2502
|
+
| Declaration | At preparation end |
|
|
2503
|
+
| --- | --- |
|
|
2504
|
+
| `atDeadline: "calls"` | The provider initiates dialing. The host never sends a timer-triggered Call. |
|
|
2505
|
+
| `atDeadline: "host-calls"` | The host sends the ordinary `call` command with a fresh host `dialId`. The provider does not independently auto-dial this task. |
|
|
2506
|
+
| `atDeadline: "waits"` | Neither side auto-dials. The task remains in preview until the agent presses Call. |
|
|
2507
|
+
|
|
2508
|
+
The agent may press Call early in either fixed-preparation dialing mode, or at any time in
|
|
2509
|
+
`waits` mode. With `waits`, the countdown is a preparation target, not an expiry: when it reaches
|
|
2510
|
+
zero the UI says "Waiting for agent" and keeps Call available. There is no automatic task-ended,
|
|
2511
|
+
disposal, dialing or phase change. No repeating timer action occurs on later snapshots. This differs
|
|
2512
|
+
from unlimited preparation only by displaying a preparation target. The former `expires` deadline
|
|
2513
|
+
value is not supported; elapsed preparation must not withdraw the task. For `calls` and `host-calls`, the deadline ends
|
|
2514
|
+
preparation and triggers initiation; it does not promise ringing, playable audio or customer
|
|
2515
|
+
answer at that exact instant. Scheduling/dispatch delay must remain visible, and a failed or
|
|
2516
|
+
prevented initiation must be reported rather than leaving a silently expired countdown.
|
|
2517
|
+
A source eligibility threshold that may never trigger an attempt is not this promise.
|
|
2518
|
+
|
|
2519
|
+
The two deadline fields travel together and only in preview. The host uses trusted provider-domain
|
|
2520
|
+
time; clock estimation is allowed only under the explicit host-triggered preview exception below.
|
|
2521
|
+
Without usable provider-domain time
|
|
2522
|
+
it cannot safely auto-trigger and must expose the uncertainty and reconcile. No zero-duration,
|
|
2523
|
+
receipt-time or local-default deadline is inferred. Neither elapsed time nor submission changes
|
|
2524
|
+
the task phase: subsequent authoritative events state ringing, answer, media and completion.
|
|
2525
|
+
|
|
2526
|
+
A host must serialize manual clicks and its timer under the exact provider/login/task/allocation
|
|
2527
|
+
scope, allowing at most one unresolved submission. Recheck the current phase, deadline owner,
|
|
2528
|
+
allocation and whether an attempt already exists before dispatch. A new snapshot or policy update
|
|
2529
|
+
cancels an obsolete timer; login loss, disconnect, task end and allocation replacement fence its
|
|
2530
|
+
callback. Reconnect past a deadline first reconciles current task and attempt state; it must not
|
|
2531
|
+
blindly submit again. An uncertain prior submission remains unresolved, without automatic retry.
|
|
2532
|
+
The provider atomically arbitrates any residual manual/timer race and does not accept a second
|
|
2533
|
+
command as ownership of an already-running independent attempt.
|
|
2534
|
+
|
|
2535
|
+
Host-triggered automatic dialing is an ordinary host dial: its result is `dialling` with the same
|
|
2536
|
+
host `dialId`, followed by exactly one correlated terminal `dial-outcome`. A provider-triggered
|
|
2537
|
+
dial has no invented host ID or host dial outcome. It reports source-evidenced party ringing,
|
|
2538
|
+
actual media and customer answer separately. In preview with `atDeadline: "calls"`, a ringing
|
|
2539
|
+
party without a host ID may carry actual pre-answer media; the deadline alone permits no media.
|
|
2540
|
+
Media is still introduced/ended by the corresponding events and must not imply customer answer.
|
|
2541
|
+
Submission acceptance is never an answered outcome or permission to publish `in-progress` early.
|
|
2542
|
+
Unsupported source correlation/outcome/media evidence remains an integration blocker.
|
|
2477
2543
|
|
|
2478
2544
|
**Drop both fields when the phase moves.** A provider that builds the `in-progress` task by
|
|
2479
2545
|
spreading the `preview` one carries `previewEndsAt` and `atDeadline` with it, and the host refuses
|
|
@@ -2482,6 +2548,33 @@ provider is told through `refused` exactly which rule, so the state that looked
|
|
|
2482
2548
|
is named on its side. The task past preview has no deadline to wait for, so it carries neither
|
|
2483
2549
|
field.
|
|
2484
2550
|
|
|
2551
|
+
**Provider execution and host controls are distinct.** Answer, hold/resume, transfer (including
|
|
2552
|
+
IVR routing), conference and handling completion are executed by the provider
|
|
2553
|
+
through the adapter. The task's applicable capability, phase, completion mode and command-specific
|
|
2554
|
+
prerequisites decide which controls are offered. Caller disconnect also executes at the provider. Microphone mute is host-owned, never a provider
|
|
2555
|
+
TaskCommand or task capability. The separately declared host recording facility keeps its existing
|
|
2556
|
+
recording contract; this distinction does not remove it or move provider call operations to the host.
|
|
2557
|
+
|
|
2558
|
+
A caller journey may revisit the same agent while an earlier handling still wraps. Public task
|
|
2559
|
+
IDs must remain distinct for concurrently open handlings. For source-owned handling IDs allocated
|
|
2560
|
+
before offer and never reused per assignment, both public task ID and allocation ID may equal that
|
|
2561
|
+
handling ID. Restore them unchanged on reconnect. Commands, media, endings and handling reports
|
|
2562
|
+
retain the exact pair; never retarget an old command to the newest handling of a journey.
|
|
2563
|
+
`validateTaskCommandRequest` checks the request's pair against the supplied published task and
|
|
2564
|
+
then its command prerequisites. The caller must select that task within the correct provider/login
|
|
2565
|
+
and recheck at the provider; the validator cannot prove source freshness or authorization.
|
|
2566
|
+
`task-ended` retires only that handling's resources. Another handling, caller channel or IVR/queue
|
|
2567
|
+
portion must not be cleared because this task ended. Snapshot counts include all still-owned wrap
|
|
2568
|
+
and active handlings; task disposal is not caller hangup.
|
|
2569
|
+
|
|
2570
|
+
**Voice describes the agent's handling within the wider call.** The task defines that agent's
|
|
2571
|
+
workspace, tools, permissions, media participation and completion work. The caller's channel may
|
|
2572
|
+
continue through IVR, queues, other agents, holds or conferences while this handling ends or wraps.
|
|
2573
|
+
Disposing this task ends this handling responsibility; it is not evidence that the caller's channel
|
|
2574
|
+
or journey ended. Task media describes this agent's attachment, not the lifetime of every party's
|
|
2575
|
+
connection. Commands retain their explicit targets and effects; task disposal must not silently
|
|
2576
|
+
become a caller-disconnect operation.
|
|
2577
|
+
|
|
2485
2578
|
**A task is never its audio.** A voice task is the allocation: the call is offered when it is
|
|
2486
2579
|
routed to the agent and accepted as its `acceptance` dictates, and its presence and phase follow
|
|
2487
2580
|
the provider's reports about the work — never the audio. Wherever audio moves — an offer, a hold, a
|
|
@@ -2688,12 +2781,27 @@ platform cannot hold a leg the host reported keeps it in the login's `store` for
|
|
|
2688
2781
|
task, so a reload of the host restates the same record; the entry is keyed by `step` and `at`, so
|
|
2689
2782
|
a running hold restated with its final `seconds` is the same entry.
|
|
2690
2783
|
|
|
2691
|
-
**The
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2784
|
+
**The provider owns the timestamps in its record.** Host timestamps are advisory. The provider
|
|
2785
|
+
may retain a host instant it accepts or assign its own observation/receipt instant; it need not
|
|
2786
|
+
copy the host clock. A provider declaring `timestampAuthority: "provider"` uses its own timestamps
|
|
2787
|
+
for final records. Receipt time is an observation boundary, not a claim about when the physical
|
|
2788
|
+
host action happened. Records are ordered by their published provider-selected instants, with no
|
|
2789
|
+
rewriting of an already-published entry when a later host report arrives.
|
|
2790
|
+
|
|
2791
|
+
The incoming host `step` and `at`, scoped to the task and its current life, identify one reported
|
|
2792
|
+
leg for correlation only. The provider retains a binding from that key to its chosen history
|
|
2793
|
+
`at`. Every successful `recordStep` returns `{ status: "recorded", at }` with that same canonical
|
|
2794
|
+
history instant, even for subsequent duration/end reports. The host keeps sending the original
|
|
2795
|
+
report key, never the returned history timestamp. A changed host timestamp is not a correction
|
|
2796
|
+
to the same report: it names a different leg. Bindings survive reconnect/reload for the retained
|
|
2797
|
+
task lifetime. The host renders provider-published history unchanged. It never compares the
|
|
2798
|
+
provider instant back to its own clock, substitutes its own timestamp, or adjusts later messages
|
|
2799
|
+
to match an earlier host estimate. The acknowledgment identifies the provider record for
|
|
2800
|
+
correlation/conformance only; it is not a host-side timestamp reconciliation instruction. Conflicting reuse is refused visibly, not paired by arrival time or nearest instant.
|
|
2801
|
+
If two distinct entries would collide under the history's `(step, at)` key, do not invent a time
|
|
2802
|
+
or combine them: report the representation conflict. No ambiguous event is silently accepted.
|
|
2803
|
+
|
|
2804
|
+
**Handle time is anchored, not restarted.** It runs from the
|
|
2697
2805
|
`answered` step's `at` — from the task's first `in-progress` where the provider reports no
|
|
2698
2806
|
history — until the task's media ends, and a hold neither pauses nor resets it: the hold's own
|
|
2699
2807
|
duration is the `held` entry's `seconds`, and a desk that restarts its counter on resume is
|
|
@@ -2776,8 +2884,9 @@ void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy:
|
|
|
2776
2884
|
void connection.recordStep?.({ taskId, allocationId, step: "muted", at, mutedBy: "host", seconds: 42, ended: true }); // the moment they unmute
|
|
2777
2885
|
```
|
|
2778
2886
|
|
|
2779
|
-
|
|
2780
|
-
|
|
2887
|
+
Every report about one leg repeats its original `step` and host `at` as a correlation key;
|
|
2888
|
+
the published history uses the provider-selected `at` returned by `recordStep`. The host reports
|
|
2889
|
+
the action it performed and its measured duration, not an authoritative provider timestamp. It may say how long so far — `seconds` is
|
|
2781
2890
|
elapsed while the leg runs and final once it has ended — and **the end is stated, never
|
|
2782
2891
|
inferred**: `ended: true` marks the last report, and it carries the final duration. **What a
|
|
2783
2892
|
provider never asked for never crosses.** The running report is sent only to a provider whose
|
|
@@ -2787,16 +2896,33 @@ a running one it was never asked for (`handlingReport.running.unexpected`). What
|
|
|
2787
2896
|
did ask for them forwards upstream, and how often, is its own business. The step appears in
|
|
2788
2897
|
`handlingHistory` when the *provider* publishes it: Omni never writes the record itself.
|
|
2789
2898
|
`recordStep` is required of every softphone login's connection, since every call on a softphone
|
|
2790
|
-
can be muted by the host, and answers `recorded`. A `muted` report says whose the silence was,
|
|
2899
|
+
can be muted by the host, and answers `recorded` with the canonical history `at`. A `muted` report says whose the silence was,
|
|
2791
2900
|
`mutedBy: "host"` or `"station"`, and no other report has the word (`handlingReport.mutedBy`,
|
|
2792
2901
|
`.mutedBy.unexpected`). On a desk phone the microphone is the phone's:
|
|
2793
2902
|
the host mutes nothing and records nothing.
|
|
2794
2903
|
|
|
2795
|
-
**
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
the
|
|
2799
|
-
the
|
|
2904
|
+
**The provider's confirmed end is decisive.** The host keeps local state for interaction and
|
|
2905
|
+
reporting, but follows the provider's authoritative state. When the provider publishes the end
|
|
2906
|
+
of the current mute leg, the host ends that local mute and releases its host-controlled mute
|
|
2907
|
+
on the matching task's media. It does not wait for its own timer, reopen the leg, replace the
|
|
2908
|
+
provider timestamp, or overwrite the provider's final duration with a later host report.
|
|
2909
|
+
A later agent mute is a new leg, never a reopening of the ended one.
|
|
2910
|
+
|
|
2911
|
+
Correlate the provider-published `muted` entry using the provider-selected `at` acknowledged
|
|
2912
|
+
for the current report key; a final `seconds` closes that leg under the history contract.
|
|
2913
|
+
This is identity matching, not reconciliation between clocks. An old closed entry, an unrelated
|
|
2914
|
+
task, or a stale connection cannot end a newer local mute. If publication precedes the acknowledgment,
|
|
2915
|
+
retain the current provider view and apply the matching closure once correlation is available;
|
|
2916
|
+
never guess a match from arrival order or nearest timestamp. Failure to release a host-controlled
|
|
2917
|
+
mute is reported visibly; local device reports still describe the actual device state.
|
|
2918
|
+
|
|
2919
|
+
At a provider-confirmed task/media end, the host also stops the associated local mute and reports
|
|
2920
|
+
its observed ending where the report is still accepted. A host closing report repeats its original
|
|
2921
|
+
key and may include its measured duration, but cannot reverse an already confirmed provider end.
|
|
2922
|
+
The provider acknowledges a known closed leg without rewriting its final record; after the task
|
|
2923
|
+
has been disposed, it may refuse the report as task-not-found. The host reads that response rather
|
|
2924
|
+
than retrying or recreating the task. An observed host end before a provider closure is still
|
|
2925
|
+
reported normally; the provider decides the final timestamp and publishes the record.
|
|
2800
2926
|
|
|
2801
2927
|
### Browser capability
|
|
2802
2928
|
|
|
@@ -3037,7 +3163,7 @@ See **Which commands need a capability**.
|
|
|
3037
3163
|
| --- | --- | --- |
|
|
3038
3164
|
| `decline` | Pending-task button: Decline | The provider can decline a pending voice offer. Omni shows it only when provisioning also permits declining. |
|
|
3039
3165
|
| `hold` | Primary toggle: Hold | Omni may issue voice-task `hold` and `resume` commands. |
|
|
3040
|
-
| `endCall` | Primary button: End call |
|
|
3166
|
+
| `endCall` | Primary button: End call | The provider ends the caller connection and all owned/inherited agent-added channels; wrap/disposal remain separate. See **Ending a call, and removing one person from it**. |
|
|
3041
3167
|
| `connectBack` | Completing-task button: Connect back | Omni may have the provider connect the agent back to the task's party while the task is `completing`, returning it to `in-progress` on the same task. Not offered where there is no `completing` window: `provider-automatic` with a zero allowance disposes at provider end. See **Connecting back during completion**. |
|
|
3042
3168
|
| `coldTransfer` | Secondary menu item: Cold transfer | Omni may hand the customer straight to a destination, with nobody spoken to first. |
|
|
3043
3169
|
| `warmTransfer` | Secondary menu item: Warm transfer | Omni may park the customer and call a destination first, then hand the customer over or cancel back. See **Warm transfer**. |
|
|
@@ -3103,12 +3229,12 @@ kind travels, nothing is typed, and a control with an empty directory has nothin
|
|
|
3103
3229
|
refused (`task.destinations.offer`). A control declared as bare `true` is refused for the same
|
|
3104
3230
|
reason (`task.destinations.shape`): once nothing is typed, the directory is the control.
|
|
3105
3231
|
|
|
3106
|
-
**
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
The
|
|
3111
|
-
|
|
3232
|
+
**The provider chooses the supported destinations.** A published item may route to an IVR,
|
|
3233
|
+
queue, named agent or another configured destination. No destination kind is inferred from its
|
|
3234
|
+
label. The host offers only the directory attached to this task's specific transfer capability,
|
|
3235
|
+
and sends the exact destination ID; it does not invent an agent picker or arbitrary dial target.
|
|
3236
|
+
The provider rechecks eligibility and executes the routing. Returning to an IVR or queue does not
|
|
3237
|
+
end the caller journey; a later assignment to the same agent is a new handling.
|
|
3112
3238
|
|
|
3113
3239
|
#### Ending a call, and removing one person from it
|
|
3114
3240
|
|
|
@@ -3116,18 +3242,44 @@ A call has everyone on `Task.onCall`, and two commands take people off it, both
|
|
|
3116
3242
|
provider.
|
|
3117
3243
|
|
|
3118
3244
|
```ts
|
|
3119
|
-
{ type: "end-call" } //
|
|
3245
|
+
{ type: "end-call" } // provider ends caller and owned/inherited agent-added channels; wrap/disposal are separate
|
|
3120
3246
|
{ type: "conference", action: "remove", party: true } // the customer leaves; the agent stays with the colleague
|
|
3121
3247
|
{ type: "conference", action: "remove", destinationId: "tier2" } // the conferenced person leaves; ringing, this calls the dial off
|
|
3122
3248
|
```
|
|
3123
3249
|
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
the
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3250
|
+
The host sends the ordinary task-scoped `execute` request with `command: { type: "end-call" }`.
|
|
3251
|
+
The provider knows the caller and agent-added channel bindings; no channel list or owner details
|
|
3252
|
+
are sent by the host. The task's `endCall` capability is permission, not an executor selection.
|
|
3253
|
+
`validateTaskCommandRequest` checks the exact handling/allocation and delegates the command's
|
|
3254
|
+
policy/phase checks. The provider rechecks current ownership atomically; no client validator can
|
|
3255
|
+
prove that its view is fresh. The host does not implement agent end-call; low-level removal of individual channels is not
|
|
3256
|
+
this agent action. There is no host end-call execution API or fallback route.
|
|
3257
|
+
|
|
3258
|
+
`end-call` is gated by `endCall` on the current handling. It ends the caller connection and
|
|
3259
|
+
all agent-added channels owned by that handling, including channels originally added by previous
|
|
3260
|
+
agents and inherited through transfer or takeover. Ownership is not inferred from who originally
|
|
3261
|
+
dialed, the current UI selection, or a person's label. The provider maintains that authority
|
|
3262
|
+
and checks the exact handling/allocation when acting. Agent-added channels are not left behind
|
|
3263
|
+
merely because another agent added them.
|
|
3264
|
+
|
|
3265
|
+
On a successful transfer or takeover, ownership of those channels passes to the receiving
|
|
3266
|
+
handling (lead or another agent), not just ownership of the caller leg. The outgoing handling may
|
|
3267
|
+
still have its allowed wrap work, but cannot disconnect channels it no longer owns. A pending or
|
|
3268
|
+
unknown handover is not a confirmed ownership change: retain source reservations and reconcile.
|
|
3269
|
+
The provider must serialize handover and end-call so a delayed outgoing command cannot tear down
|
|
3270
|
+
the recipient's call. No public channel-owner field or guessed identity is introduced here;
|
|
3271
|
+
the source must provide authoritative ownership and command fencing before declaring permission.
|
|
3272
|
+
|
|
3273
|
+
The resulting end of this agent's media is published as `task-media-ended`. Any wrap remains
|
|
3274
|
+
until its own completion/disposal; `end-call` is not `task-ended`, and `complete` is not an implicit
|
|
3275
|
+
caller/channel disconnect. Other agents' independent tasks end or wrap only on their own source
|
|
3276
|
+
transitions. Neither disposal nor transfer itself ends the continuing caller journey.
|
|
3277
|
+
|
|
3278
|
+
`conference` `remove` remains a person-specific provider operation, gated by `conference`.
|
|
3279
|
+
It does not implicitly dispose any other handling. Its person/consultation prerequisites
|
|
3280
|
+
remain separate from the permission to disconnect the caller. A remove that would leave the
|
|
3281
|
+
agent alone is not a remove but an `end-call`, and the provider answers it `failed`.
|
|
3282
|
+
The consulted destination of a warm transfer is not removed this way:
|
|
3131
3283
|
`transfer` `cancel` is its own step, which returns the agent to the parked customer, and a remove
|
|
3132
3284
|
never promises that.
|
|
3133
3285
|
|
|
@@ -3355,14 +3507,12 @@ the platform added itself carries none. `held: true` is presence as claim. A sna
|
|
|
3355
3507
|
after a transfer reads the room from here rather than inferring it from a sequence of outcomes it
|
|
3356
3508
|
never saw.
|
|
3357
3509
|
|
|
3358
|
-
**`onCall`
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
say who is, which is a different claim, so a provider that publishes the room publishes one last
|
|
3365
|
-
change: the empty room.
|
|
3510
|
+
**`onCall` describes this agent's current handling.** When the caller disconnects but the agent
|
|
3511
|
+
and added channels remain connected, the remaining room is still valid. When this handling's
|
|
3512
|
+
media ends or the task enters `completing`, clear its `onCall` view; absence is used only by a
|
|
3513
|
+
provider that never publishes the room. This does not assert that the caller, bridge or other
|
|
3514
|
+
agents' channels ended. A published room receives a final empty view for this handling, and the
|
|
3515
|
+
task may remain open for wrap. Disposal is a separate task action, not the caller's disconnect.
|
|
3366
3516
|
|
|
3367
3517
|
**A field that describes the present is cleared by the transition that ends it.** `onCall`,
|
|
3368
3518
|
`previewEndsAt` and `atDeadline` are three instances of one shape, and there will be more: each
|
|
@@ -3552,6 +3702,40 @@ against it (`stream.taskOffered.overCapacity`).
|
|
|
3552
3702
|
idle dialpad arrives through `task-offered` like any other task, and a full agent does not forbid
|
|
3553
3703
|
it: the ceiling binds allocation, not the agent's own hand.
|
|
3554
3704
|
|
|
3705
|
+
### Runtime break prerequisite checks
|
|
3706
|
+
|
|
3707
|
+
Use `validateBreakCommand(method, request, state, context)` from the validation entry point
|
|
3708
|
+
before any agent break dispatch. The context supplies current authentication and transport;
|
|
3709
|
+
all four methods require the live login's break capability and active transport. Pass the
|
|
3710
|
+
request object only to the request method, and undefined to the other three.
|
|
3711
|
+
|
|
3712
|
+
| Method | Required current state |
|
|
3713
|
+
| --- | --- |
|
|
3714
|
+
| `requestBreak` | `not-requested`; selected current reason code when codes exist; `mayAsk` or the selected reason's `alwaysAvailable` exception. Free text does not replace a code. |
|
|
3715
|
+
| `commitBreak` | `granted`, or already committed for an idempotent repeat. A later change to `mayAsk` does not revoke the grant. |
|
|
3716
|
+
| `cancelBreak` | `awaiting-decision` or `granted`. A concurrent commit winning still answers `omni.break-already-committed` and requires recovery. |
|
|
3717
|
+
| `endBreak` | `in-effect` or `starting-after-task` during reconciliation; an agent cannot end an imposed break. |
|
|
3718
|
+
|
|
3719
|
+
Use `validateTeamBreakCommand(request, context)` for lead decisions, placement, release and
|
|
3720
|
+
policy commands. It requires the live lead capability and active transport, a current target
|
|
3721
|
+
roster for member commands, and the target's complete break state for placement/release.
|
|
3722
|
+
Approve/deny requires an awaiting decision; placement uses the target's reason codes; release
|
|
3723
|
+
requires an imposed committed break. The context's target state must belong to the named member;
|
|
3724
|
+
that association and backend authorization are provider responsibilities.
|
|
3725
|
+
|
|
3726
|
+
Use `validateBreakStatus(state, tasks)` on the complete retained task view after each transaction,
|
|
3727
|
+
as well as `validateBreakTransition` for event ordering. Snapshot validation shares the status
|
|
3728
|
+
checks. Pending and completing tasks still count as work. Only the existing monitoring exception
|
|
3729
|
+
allows tasks beside a break in effect. Validate full task shapes and the envelope separately.
|
|
3730
|
+
|
|
3731
|
+
These validators report violations without dispatching, rewriting state or proving freshness.
|
|
3732
|
+
Recheck prerequisites atomically at the provider; client validation cannot prevent a race.
|
|
3733
|
+
Continue to use `validateResult` for each method's response vocabulary. A request acknowledgment
|
|
3734
|
+
is not a grant, and no method response overwrites a newer state emission. Rejected promises
|
|
3735
|
+
remain unknown and recover by snapshot, not automatic retry. The host's frozen provider set,
|
|
3736
|
+
durable mutually exclusive commit/cancel decision and reconciliation obligations above remain
|
|
3737
|
+
required; per-provider validation does not implement the multi-provider coordinator.
|
|
3738
|
+
|
|
3555
3739
|
### `requestBreak(request)`
|
|
3556
3740
|
|
|
3557
3741
|
Requests permission to stop the agent later; it does not itself stop work. The provider continues
|
|
@@ -4273,7 +4457,7 @@ an affordance, never the enforcement: the host asks, and the provider does or de
|
|
|
4273
4457
|
answers `failed` for a command whose capability it did not publish, and that is the provider
|
|
4274
4458
|
honouring its own declaration, not the host deciding policy -- a host that refuses a command on
|
|
4275
4459
|
its own reading of a queue's flag has decided something that was never its to decide. What a
|
|
4276
|
-
command means on the platform is the provider's to work out: `end-call`
|
|
4460
|
+
command means on the platform is the provider's to work out: `end-call` disconnects the caller channel, and
|
|
4277
4461
|
which legs on which bridge that touches is a fact about the switch, never a choice the host makes.
|
|
4278
4462
|
The one thing the host performs physically is the microphone, and that is not a command at all.
|
|
4279
4463
|
|
|
@@ -4375,12 +4559,13 @@ declared:
|
|
|
4375
4559
|
|
|
4376
4560
|
**A control on the contact belongs to the handling phases**, `in-progress` and `paused`:
|
|
4377
4561
|
`hold`, `resume` and `pause`, `end-call`, every `transfer` and `conference` action, and
|
|
4378
|
-
every `lead-assist` action.
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4562
|
+
every `lead-assist` action. These controls act within this agent's current handling.
|
|
4563
|
+
In `completing`, that handling has ended and its wrap work remains. The caller may still be in
|
|
4564
|
+
an IVR, queue or another agent's handling, and other channels may remain connected. Completion
|
|
4565
|
+
of this handling does not establish that the caller or bridge ended. The capability stays
|
|
4566
|
+
declared while the task remains open; the phase prevents this task from controlling a handling
|
|
4567
|
+
it no longer owns. Omni shows these controls only in the two handling phases, and
|
|
4568
|
+
`validateTaskCommand` refuses them outside those phases (`command.phase.handling`).
|
|
4384
4569
|
The commands with a phase of their own -- `answer`, `accept` and `decline` in `pending`,
|
|
4385
4570
|
`call` in `preview`, `connect-back` in `completing`, `complete` in any -- are not among them.
|
|
4386
4571
|
|
|
@@ -4460,22 +4645,102 @@ They are published as `OMNI_FAILURE_CODES`.
|
|
|
4460
4645
|
| `occurredAt` | Valid RFC-3339 timestamp with an explicit timezone, representing provider observation time. |
|
|
4461
4646
|
| `event` | Typed `ProviderEvent` payload. |
|
|
4462
4647
|
|
|
4463
|
-
####
|
|
4648
|
+
#### Timestamp standard and explicit exceptions
|
|
4464
4649
|
|
|
4465
|
-
Every
|
|
4466
|
-
`
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4650
|
+
Every provider event envelope carries `occurredAt` as an ISO timestamp in the contract's RFC-3339
|
|
4651
|
+
form, with `Z` or an explicit numeric timezone offset. Preserve the original instant. Payloads
|
|
4652
|
+
carry their source event/observation instants wherever available and declared by their type;
|
|
4653
|
+
publication and receipt time must not be substituted for a missing source instant. A required
|
|
4654
|
+
instant that the source cannot establish makes that evidence unavailable, not permission to
|
|
4655
|
+
invent a timestamp or silently omit a required field.
|
|
4471
4656
|
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4657
|
+
Clock estimation is an exception, not the standard interpretation of events. An ISO timestamp
|
|
4658
|
+
identifies an instant; it does not prove that the host and provider clocks are synchronized.
|
|
4659
|
+
Use an explicitly trusted clock in the relevant domain. Do not infer a general clock offset
|
|
4660
|
+
from event arrivals, shift source timestamps, or use an estimate to order events or create history.
|
|
4476
4661
|
|
|
4477
|
-
|
|
4478
|
-
|
|
4662
|
+
The current exceptions and their reasons are:
|
|
4663
|
+
|
|
4664
|
+
| Case | Exception and reason |
|
|
4665
|
+
| --- | --- |
|
|
4666
|
+
| Host display of provider deadlines (`allocationExpiresAt`, `previewEndsAt`, wrap deadline) | A countdown may use an explicitly bounded provider-clock estimate when a trusted direct clock is unavailable, because the deadline belongs to another clock domain. This estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
|
|
4667
|
+
| Host-triggered preview deadline | A bounded clock estimate with monotonic aging may schedule the host's Call command because the provider owns the deadline but the host owns the trigger. Do not trigger before the deadline is known to have passed; invalidate on clock discontinuity and reconcile when uncertain. |
|
|
4668
|
+
| Recording evidence expiry | A bounded observer-domain clock estimate with monotonic aging may assess freshness because observation and expiry belong to the recorder's clock. If time cannot be trusted, recording state is unknown; never renew evidence from receipt or replay. |
|
|
4669
|
+
| Unknown recording state | `observedAt` and `validUntil` are absent because there is no confirmed observation. The containing provider event still has its own `occurredAt`; that publication is not a recorder observation. |
|
|
4670
|
+
| Direct snapshot reads and method requests/results | These are reads/operations, not event envelopes, and their current types have no general event timestamp. A snapshot event still carries `occurredAt`; embedded source instants remain unchanged. Do not treat a method result or read completion time as an occurrence boundary. |
|
|
4671
|
+
| Host-provided provider-clock estimate | Best-effort ISO time may be supplied by the host for optional comparisons; it is not a source observation or accuracy guarantee. The reason is that the host does not own the provider clock. |
|
|
4672
|
+
| Provider receipt timestamps | The provider may use its own receipt instant for final records of receipt/processing because host timestamps are untrusted advisory input. Receipt must not be presented as an earlier action or capture boundary. |
|
|
4673
|
+
| Optional source instants and deadlines | Omit only where the declared type permits absence and the source has no evidence or the policy has no deadline (for example unlimited preview). Do not replace absence with host time. |
|
|
4674
|
+
|
|
4675
|
+
Every additional exception must be listed with its scope and reason before adoption. Estimates
|
|
4676
|
+
must specify their uncertainty and validity; elapsed time should use monotonic aging, and a clock
|
|
4677
|
+
jump invalidates the estimate. The optional check below samples provider time without changing event timestamps. Report
|
|
4678
|
+
source-measured durations where required; timestamp subtraction is not a substitute.
|
|
4679
|
+
|
|
4680
|
+
#### Optional periodic provider time checks and host estimates
|
|
4681
|
+
|
|
4682
|
+
A provider may declare `Manifest.timeCheck: true` and implement `Connection.checkTime(request)`.
|
|
4683
|
+
Omission means unsupported. The host separately opts in with `ProviderTimeCheckPolicy`; these
|
|
4684
|
+
host-local settings are not task policy or a guarantee. All four durations are explicit positive
|
|
4685
|
+
safe integer milliseconds, with `maxRoundTripMs <= timeoutMs <= intervalMs`. No default interval
|
|
4686
|
+
is assumed. Validate settings with `validateProviderTimeCheckPolicy`.
|
|
4687
|
+
|
|
4688
|
+
While the connection is active, the host checks immediately on opt-in and then at `intervalMs`
|
|
4689
|
+
using a monotonic scheduler. Only one check may be outstanding. Missed ticks are skipped, never
|
|
4690
|
+
replayed in a burst. Each request has a fresh ID. The provider samples its own event/deadline
|
|
4691
|
+
clock after receiving the request and before returning `providerTime` as an ISO/RFC3339 instant,
|
|
4692
|
+
with the request ID, authenticated login ID and `clockId`. No cached sample or adapter-local time
|
|
4693
|
+
may masquerade as provider time. A provider unable to sample that domain must not declare support.
|
|
4694
|
+
Clock authority replacement or a discontinuity changes `clockId`.
|
|
4695
|
+
|
|
4696
|
+
Validate requests and responses with `validateProviderTimeCheckRequest` and
|
|
4697
|
+
`validateProviderTimeCheckResult`. The host also checks the outstanding request, provider/connection
|
|
4698
|
+
and login, monotonic elapsed time and cancellation after the await: a late successful response is
|
|
4699
|
+
still rejected. Reject samples at or beyond `timeoutMs`, or above `maxRoundTripMs`. Cancel local
|
|
4700
|
+
tracking and invalidate estimates on disconnect, logout, policy disable, clock change or clock
|
|
4701
|
+
jump; late results cannot restore them. Resume with a fresh check after reconnection. Expire a
|
|
4702
|
+
sample at `maxSampleAgeMs` using monotonic elapsed time. Failure is visible through the host's
|
|
4703
|
+
existing diagnostic path, with the estimate unavailable; the next scheduled check can recover.
|
|
4704
|
+
Checking failure alone does not assert call/recording state or require a healthy connection to close.
|
|
4705
|
+
This read-only check is not a task command. Protocol supplies declarations and validators; the
|
|
4706
|
+
host implements the scheduler and the provider implements the clock read.
|
|
4707
|
+
|
|
4708
|
+
The host may also expose `Host.estimateProviderTime({ providerId, loginId })`. It returns an ISO
|
|
4709
|
+
`at` in that provider's clock domain, the same explicit scope and `clockId`, or `undefined` when
|
|
4710
|
+
unavailable. Optional `uncertaintyMs` is an estimate, not a guaranteed bound. The method is optional
|
|
4711
|
+
at the host level and is deliberately absent from `HostGuarantees`. A provider declaring time
|
|
4712
|
+
checks does not require the host to expose estimates, and an estimate need not originate from
|
|
4713
|
+
this check if the host has another explicitly configured source for that provider clock.
|
|
4714
|
+
|
|
4715
|
+
To estimate the difference, the host brackets the request with local send/receive instants and
|
|
4716
|
+
measures elapsed time monotonically. For provider sample P and local bracketing instants H0/H1,
|
|
4717
|
+
the offset under stable clocks lies between P-H1 and P-H0, before clock error and timestamp
|
|
4718
|
+
precision are considered. A midpoint is only an estimate; one-way delays need not be symmetric.
|
|
4719
|
+
Never treat round-trip uncertainty as proof of the source clock's accuracy. Advance a retained
|
|
4720
|
+
sample with monotonic elapsed time, and return unavailable after its configured validity ends.
|
|
4721
|
+
`validateProviderTimeEstimate` checks shape and scope, not actual accuracy or freshness.
|
|
4722
|
+
|
|
4723
|
+
A provider may declare `Manifest.timestampAuthority: "provider"`: its own timestamps are
|
|
4724
|
+
used for final records, and host-supplied timestamps are advisory. This declaration is independent
|
|
4725
|
+
of time-check support and the optional host estimate; neither changes who owns the final record.
|
|
4726
|
+
Omission makes no promise that host timestamps will be trusted. No host-authoritative default
|
|
4727
|
+
is inferred. Successful `recordStep` responses identify the provider-selected history instant;
|
|
4728
|
+
see **The host records what it performs** for correlation and retained bindings.
|
|
4729
|
+
|
|
4730
|
+
A provider need not trust or adopt any host-supplied timestamp. It may timestamp an incoming
|
|
4731
|
+
message using its own clock at receipt and use that for its own processing/accounting. The reason
|
|
4732
|
+
for this exception is that host clocks and host estimates are not authoritative at the provider.
|
|
4733
|
+
Receipt time must be described as receipt/observation time, not relabeled as the original host
|
|
4734
|
+
action, recorder capture boundary, or proof an operation applied. An existing field with a
|
|
4735
|
+
specific occurrence meaning still requires that evidence; this option does not silently redefine
|
|
4736
|
+
it. Host-only instants remain advisory input; the provider owns its authoritative publication.
|
|
4737
|
+
|
|
4738
|
+
This host estimate is another explicit timestamp exception: it helps optional displays and
|
|
4739
|
+
provider-clock comparisons when the host lacks that clock directly. It is not a source occurrence,
|
|
4740
|
+
may not replace event/history timestamps, and alone cannot authorize deadline actions or prove
|
|
4741
|
+
recording freshness. Uses requiring a trustworthy bound still need independently established
|
|
4742
|
+
clock accuracy/drift assumptions. It neither synchronizes the operating-system clock nor changes
|
|
4743
|
+
ISO timestamps already published by the provider.
|
|
4479
4744
|
|
|
4480
4745
|
#### Nothing is lost until the connection drops
|
|
4481
4746
|
|
|
@@ -4588,6 +4853,48 @@ from `in-effect` or `starting-after-task` to a grant or a request, or from `gran
|
|
|
4588
4853
|
`exerciseAdapter` holds the stream to that from the connect snapshot on;
|
|
4589
4854
|
`assertBreakFollowsItsRequests` holds any sequence.
|
|
4590
4855
|
|
|
4856
|
+
Adapters and hosts can call `validateBreakTransition(before, after)` from the package’s validation entry point
|
|
4857
|
+
with two complete break objects before applying an event. It checks shape and the transition
|
|
4858
|
+
rules above; it does not mutate either state or apply the event. Report a violation visibly,
|
|
4859
|
+
retain the last accepted state without claiming it remains current, and reconcile from the
|
|
4860
|
+
source. Continue to validate the full envelope/login and task/break consistency separately.
|
|
4861
|
+
The conformance `BreakStream` validates its baseline, retains the last accepted state on a
|
|
4862
|
+
rejected delta, and sets `needsRecovery`. Transport loss also requires reseeding; an `active` transport
|
|
4863
|
+
notification alone does not restore a break baseline. Until a fresh validated snapshot is seeded, further
|
|
4864
|
+
break deltas are refused (`stream.breakState.baseline`); the first delta is never an implicit
|
|
4865
|
+
snapshot. `seed(snapshot)` returns violations, which callers must check. This is a conformance
|
|
4866
|
+
tracker, not a complete production reducer: snapshot freshness, login/connection fencing and
|
|
4867
|
+
full task consistency remain separately required.
|
|
4868
|
+
|
|
4869
|
+
**Both sides must enforce the order.** The host validates immediately before dispatch; the
|
|
4870
|
+
provider rechecks current permission and state atomically before acting. The provider validates
|
|
4871
|
+
the resulting break/task state before publication and serializes those publications. The host
|
|
4872
|
+
validates the envelope, transition and full task consistency before replacing local state. A
|
|
4873
|
+
request result does not optimistically advance break state, and a delayed command cannot apply
|
|
4874
|
+
to a later attempt merely because its approval happens to look compatible. Keep at most one
|
|
4875
|
+
unresolved operation per provider/attempt; recovery must resolve uncertainty before another
|
|
4876
|
+
operation is dispatched. The source must fence delayed operations against its own attempt state.
|
|
4877
|
+
|
|
4878
|
+
Normal order is `not-requested` → `awaiting-decision` → `granted` →
|
|
4879
|
+
`starting-after-task` → `in-effect` → `not-requested`. Auto-approval may go directly to
|
|
4880
|
+
`granted`; a commit with no outstanding work may go directly to `in-effect`. Denial/cancel
|
|
4881
|
+
returns a precommit request to `not-requested`; an authorized end/release returns a committed
|
|
4882
|
+
break there. Same-state restatements are allowed. An evidenced imposed break is the explicit
|
|
4883
|
+
exception to requesting/granting, and must carry its imposed actor/state. Neither skipped
|
|
4884
|
+
publication nor a host-local guess creates another exception. A later normal attempt starts
|
|
4885
|
+
from `not-requested`, never by regressing an active break into a request.
|
|
4886
|
+
|
|
4887
|
+
A fresh authoritative snapshot establishes a new baseline; do not run this event-transition
|
|
4888
|
+
check across it. A snapshot may legitimately establish a later request or an already active
|
|
4889
|
+
break after reconnect. Its freshness must be established by the source/adapter's ordered
|
|
4890
|
+
snapshot/publication boundary and fencing of obsolete callbacks and reads. A delayed old
|
|
4891
|
+
snapshot is not detectable from break approval alone. Neither envelope time nor event ID
|
|
4892
|
+
supplies a break revision. Do not suppress all requests after an active break: a later attempt
|
|
4893
|
+
passes through `not-requested`, and then may request again. Do not replay earlier ProviderEvents
|
|
4894
|
+
after recovery. Raw backend break handlers must preserve source order too; this helper does
|
|
4895
|
+
not implement or assume invocation of Protocol's two-phase request/commit coordinator.
|
|
4896
|
+
|
|
4897
|
+
|
|
4591
4898
|
For a multi-provider break attempt, "every provider" is the set of providers frozen when the
|
|
4592
4899
|
attempt entered `requesting-break`. Omni commits only after every asked provider reports `granted` —
|
|
4593
4900
|
that one is unconditional, because nothing has stopped yet and waiting costs only time. It enters
|
|
@@ -4766,6 +5073,10 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
4766
5073
|
| `validateContact(contact)` | Contact field shapes and attribute keys. Every field is optional, so this checks what is present rather than what is missing. |
|
|
4767
5074
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|
|
4768
5075
|
| `validateHostGuarantees(guarantees)` | What a host promises: only the guarantees this contract names, each declared by presence and never `false`. The harness validates the guarantees of whatever host a test hands the adapter. |
|
|
5076
|
+
| `validateProviderTimeCheckPolicy(policy)` | Explicit optional polling settings, positive safe-integer durations and round-trip/timeout/interval ordering. |
|
|
5077
|
+
| `validateProviderTimeCheckRequest(request)` | Fresh-request shape; the host enforces actual uniqueness and outstanding-request lifetime. |
|
|
5078
|
+
| `validateProviderTimeCheckResult(result, request, loginId)` | ISO timestamp, clock identity, exact request/login correlation; timing and source accuracy remain runtime checks. |
|
|
5079
|
+
| `validateProviderTimeEstimate(estimate, scope)` | Optional host estimate shape and provider/login scope; no accuracy guarantee. |
|
|
4769
5080
|
| `validateHandlingReport(report, path?, manifest?)` | What the host reports of a leg it performed, for an adapter to check before forwarding: a task, a step, when it began, a positive `seconds` where stated, and an explicit `ended` that carries the final duration. Given the manifest, a running report is refused unless it declares `runningStepReports`. |
|
|
4770
5081
|
| `validateHostReport(report)` | The host's own report as published to an adapter: `online`, and where there is audio, an input that is `available` with the microphone and `flowing`, or `unavailable` with a reason and the failure that says why, and an output that is `available` or `unavailable` with its failure. The harness validates whatever host a test hands the adapter; `stillHost(report)` builds one that never changes. |
|
|
4771
5082
|
| `validateHostMute(mute, softphone)` | What the host's Mute does, stated on a softphone login and nowhere else: `stream` or `station` (`host.mute`), required where the host holds a microphone (`host.mute.required`) and refused where it does not (`host.mute.unexpected`). The harness holds `ConnectContext.host.mute` to it. |
|
|
@@ -4880,7 +5191,7 @@ as each case was considered, so a test that needs a rule to have run asserts it
|
|
|
4880
5191
|
inferring it from an empty `violations`, and a rule absent from it was never looked at, which is a
|
|
4881
5192
|
gap and not a pass. With the audio open on a softphone,
|
|
4882
5193
|
the drive mutes it for one second and reports the leg through `recordStep`, begun and then ended,
|
|
4883
|
-
expecting each report `recorded` (`drive.recordStep.failed`, `.rejected`); then it mutes again and
|
|
5194
|
+
expecting each report `recorded` with the provider-selected history `at` (`drive.recordStep.failed`, `.rejected`, `result.recordStep.at`); then it mutes again and
|
|
4884
5195
|
ends the call muted, as agents do, so the leg is open when the media ends, the provider closes it
|
|
4885
5196
|
in the completing publication or the open entry is refused (`task.handlingHistory.muted.open`),
|
|
4886
5197
|
and the drive's closing report after the media ended is expected `recorded` and to change nothing:
|
|
@@ -4890,7 +5201,7 @@ Where the provider restates the task's record afterwards, each leg is in it or t
|
|
|
4890
5201
|
does, the drive reloads the host as a reload happens: the first client is unsubscribed,
|
|
4891
5202
|
disconnected and its session closed (`drive.reload.handover`), and only then is a second adapter
|
|
4892
5203
|
built and connected for the same login with the same context and the same `store`; its snapshot
|
|
4893
|
-
must carry the task with that leg and the
|
|
5204
|
+
must carry the task with that leg at its acknowledged provider timestamp and the reported actor, and the run goes on with the second as its
|
|
4894
5205
|
connection to the end. A platform that holds the record hands it back, and an adapter that composed
|
|
4895
5206
|
the record in memory has nothing and is named (`drive.reload.snapshot`, `drive.reload.history`,
|
|
4896
5207
|
`.rejected`). The second adapter's snapshot is taken as any resync is: held to what the stream knew
|
|
@@ -4913,10 +5224,10 @@ another id (`drive.store.retained`); an adapter whose keys never named the task
|
|
|
4913
5224
|
unevaluated, and the result says so rather than passing it: a task's keys go
|
|
4914
5225
|
with the task, or the next offer of the same id inherits them. Outside the
|
|
4915
5226
|
handling phases with `hold` still declared -- in
|
|
4916
|
-
`confirmed`, where the provider publishes it, and in `completing` once
|
|
5227
|
+
`confirmed`, where the provider publishes it, and in `completing` once this agent's handling has ended -- the
|
|
4917
5228
|
drive sends `hold` past the validator that would hold it back, and expects `failed`: the adapter
|
|
4918
|
-
is the second gate on a control on the contact, and one that applies it
|
|
4919
|
-
|
|
5229
|
+
is the second gate on a control on the contact, and one that applies it outside this task's
|
|
5230
|
+
current handling is named (`drive.command.handling`). The drive cannot put a task into a phase the provider
|
|
4920
5231
|
never publishes, so a provider that goes straight from `pending` to `in-progress` is checked in
|
|
4921
5232
|
`completing` alone. A task the agent completes is
|
|
4922
5233
|
completed from wherever it stands once the drive has nothing left to do on it -- from `completing`
|
|
@@ -5050,7 +5361,8 @@ observation instant and an exclusive expiry. These times describe current eviden
|
|
|
5050
5361
|
capture boundaries. A trusted observer-domain current time must satisfy observedAt <= now < validUntil.
|
|
5051
5362
|
Use `effectiveRecordingState` with that explicitly trusted time; an unavailable clock yields unknown.
|
|
5052
5363
|
Receipt, replay and task publication never extend freshness. Clock discontinuity invalidates evidence;
|
|
5053
|
-
consumers
|
|
5364
|
+
consumers using the explicit recording-expiry clock-estimation exception must invalidate that
|
|
5365
|
+
estimate and use monotonic aging so clock rollback cannot
|
|
5054
5366
|
revive expired evidence. The executor compares observation identity and recording identity against
|
|
5055
5367
|
its latest state atomically before I/O. An intervening observation or allocation change refuses the
|
|
5056
5368
|
stale command; it never acts on a replacement recorder. Providers lacking trustworthy current-state
|