@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/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" | "expires";
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 is audio and media starts on `dialling`. |
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`: when the system stops waiting for the agent to press Call. Absent, the agent has as long as they need. Always with `atDeadline`. See **Preview: the agent presses Call**. |
2359
- | `atDeadline` | Voice only, in `preview`, with `previewEndsAt`: what the system does at the deadline -- `calls` places the call itself, `expires` takes the record back and the task ends `expired`. |
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 deadline `expires` | Removed by `task-ended` with `expired` outcome |
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, the task sits in `preview` with the party on it, and no call has gone
2441
- out. The agent reads and presses Call. That is the whole phase, and it exists on voice alone --
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
- **The deadline says what the system will do.** A provider that will not wait forever states
2470
- `previewEndsAt`, and with it `atDeadline`: `calls` means the system places the call itself when the
2471
- time runs out, which is progressive dialling; `expires` means the record is taken back and the task
2472
- ends with an `expired` outcome naming `preview`. The two travel together
2473
- (`task.preview.atDeadline.required`, `task.preview.previewEndsAt.required`), only while the task is in
2474
- `preview` (`task.preview.deadline.unexpected`), and Omni's countdown reads "calling in 12s" or
2475
- "expires in 12s" from the provider's word. With neither, the agent has as long as they need.
2476
- Reaching the instant is not itself a transition: the provider reports what it did, as an event.
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 record is ordered by the instants stated in it, whoever stamped them.** The host stamps the
2692
- legs it performs from the same clock it reports everything else with, and the provider writes a
2693
- host leg into the record with the host's `at`, in its place among the others by that instant, and
2694
- answers `recorded`: it never refuses a leg, or the task, for a timestamp it did not write. A host
2695
- whose clock runs ahead of the platform's puts its own mute before the platform's answer, and the
2696
- record shows what was stated; that is the host's clock to fix, not the provider's record to edit. **Handle time is anchored, not restarted.** It runs from the
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
- The entry is keyed by `step` and `at`, so every report about one leg names the same instant. The
2780
- host is the authority for the legs it performs, so it may say how long so far — `seconds` is
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
- **A host-performed leg still open when the task's media ends, or the task ends, is ended by the
2796
- host at that instant** — `ended: true`, `seconds` to the end, the same `at` — since a provider
2797
- left to close it would be guessing at a host-performed duration. Both ends are read off one clock,
2798
- the host's, and a leg shorter than a second is reported as the second it was, never as the nought
2799
- the record refuses.
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 | Omni may end the whole call: everyone leaves, the media ends, and the task stays for its wrap-up. See **Ending a call, and removing one person from it**. |
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
- **An agent never transfers to a named agent**, warm or cold. A contact that needs another pair of
3107
- hands goes back to a queue or a menu the queue configured, and who takes it next is the queue's
3108
- decision: routing by skill, availability and fairness is what the queue is for, and a hand-off to a
3109
- chosen colleague bypasses all three. The desk shows no agent picker and no dial pad on a transfer.
3110
- The one way a named person ends up with an agent's call is a lead taking it over through lead
3111
- assist, which is the lead's act, not a transfer the agent chose.
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" } // everyone leaves; the media ends; the task stays for its wrap-up
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
- `end-call` ends the whole conversation for everyone on it, and is gated by `endCall`. The task does
3125
- not end with it: `task-media-ended` follows, any wrap allowance runs, and the agent dispositions
3126
- the call. `conference` `remove` takes one person off, named as the room names them, and the call
3127
- goes on for the rest; it is gated by `conference`, since it only means something with a third
3128
- person on the line. Removing the party leaves the agent with the colleague, a warm hand-over in
3129
- reverse. A remove that would leave the agent alone is not a remove but an `end-call`, and a
3130
- provider answers it `failed`. The consulted destination of a warm transfer is not removed this way:
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` is who is on the call now, and a task outlives its call.** Wrap-up is not an ending:
3359
- the task is alive, the agent is working, and nothing arrives to say the call ended until they
3360
- dispose. So the room must not be the last word about a call that has ended. Once the call is over
3361
- -- the task `completing`, or its `media` ended -- `onCall` is empty, or absent only where the
3362
- provider never publishes the room at all, and a task carrying people on a call that has ended is
3363
- refused (`task.onCall.ended`). Empty says nobody is on the call; absent says the provider does not
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` ends the conversation, and
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. Each acts on the call or the conversation, and only while there is one.
4379
- Before `in-progress` nothing has been placed or opened; in `completing` the handling has ended -- a
4380
- call with nobody on it, a conversation closed -- and a wrap-up that still shows Transfer shows it
4381
- for nothing. The capability stays declared, because it is a property of the task and the task is
4382
- still open; the phase says there is nothing to use it on. Omni shows none of these controls outside
4383
- the two phases, and `validateTaskCommand` refuses each of them there (`command.phase.handling`).
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
- #### Provider instants are read against a provider clock
4648
+ #### Timestamp standard and explicit exceptions
4464
4649
 
4465
- Every deadline in this contract is a provider instant that Omni counts down: `allocationExpiresAt`,
4466
- `previewEndsAt`, and the wrap deadline of `task-media-ended` plus `wrapAllowance` where
4467
- one is stated.
4468
- Comparing those against the host clock is wrong by whatever the two machines disagree by, and the
4469
- damaging direction is early — **Accept** withdrawn from an offer still ringing, a wrap timer
4470
- expiring before the agent has finished.
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
- `occurredAt` is what fixes it. Omni notes the host time at which each envelope arrives, keeps the
4473
- running offset against the `occurredAt` inside it, and translates provider instants through that
4474
- offset before counting down. What remains is network delay, which biases every deadline later —
4475
- the direction that costs a second rather than an action.
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
- **Report `seconds`; never expect Omni to derive it** is the same hazard from the provider's side:
4478
- neither party recomputes a duration across a clock it does not own.
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 host's word, and the run goes on with the second as its
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 the call has ended -- the
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 where there is nothing to
4919
- hold is named (`drive.command.handling`). The drive cannot put a task into a phase the provider
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 must invalidate their clock estimate and use monotonic aging so clock rollback cannot
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