@xema/omni-protocol 0.1.116 → 0.1.118

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/voice.md CHANGED
@@ -262,7 +262,7 @@ provider decides: another IVR, a queue, a survey, or the end of the call. Gated
262
262
  with nobody coming back to them, and the provider's path for a caller who continues never begins.
263
263
  So the caller is taken off hold first: the agent computer holds the command back while the task is `paused`
264
264
  (`command.endCall.held`) and shows End call disabled on hold, and a provider that receives it
265
- anyway answers `failed`. Resume, then end. `terminate-call` needs no such gate: it ends the
265
+ anyway answers `failed` with `omni.on-hold`, so the agent application can say resume first. Resume, then end. `terminate-call` needs no such gate: it ends the
266
266
  caller's channel with the rest, held or not, and nobody is left waiting.
267
267
 
268
268
  **`terminate-call` ends the whole call.** Every channel on it ends at the provider: the agent's,
@@ -463,114 +463,120 @@ agent's.
463
463
  ## Independent task recording
464
464
 
465
465
  Recording is voice-only. A task can arrive already recording, including while pending, and offer
466
- no recording controls. The provider publishes only its own current state on
467
- the provider field of the task’s `recording` state. The agent application publishes its own full scoped view in `HostReport.recordings`.
468
- A provider task update cannot replace agent application state. Neither a capability nor an accepted command
469
- establishes recording. Missing state, lost recorder observation, transport loss and expired
470
- observation mean unknown; they never mean stopped. Paused means a recording remains open without
471
- capturing. Task hold, task pause and recording pause are independent.
472
-
473
- The per-task policy is `Task.capabilities.recording`, with independently optional provider and agent application
474
- action sets. The outer capability may be locked. Absence grants no permission, and a state may
475
- exist without any permission. A provider may offer only stop for a recording started automatically,
476
- or withdraw a control on a later task update. There is no global recording mode and no automatic
477
- start from a capability declaration. Agent application support is declared on `ConnectContext.host.recording`,
478
- not the provider-owned manifest. The task names one of the storage places the agent application provisioned, by `storageId`;
479
- an unknown storage place or an unsupported action is refused visibly, never redirected to provider
480
- recording or a default upload location. Initial agent application support requires voice softphone audio and
481
- capture of both local and remote audio; an agent application unable to capture either must refuse start/resume.
482
- This package declares that contract; it supplies no recorder, audio mixing, storage or upload.
483
-
484
- | Action | Required current recording state | Confirmed outcome |
485
- | --- | --- | --- |
486
- | start | inactive | active with a new recording identity |
487
- | pause | active | paused, preserving recording identity and captured audio |
488
- | resume | paused | active with the same recording identity |
489
- | stop | active or paused | inactive; finish and retain captured audio |
490
- | cancel | active or paused | inactive; abandon and discard captured audio |
491
-
492
- Stop finishes the recording and retains captured audio. Cancel abandons the recording and discards
493
- its captured audio; the task policy offers it with `cancel: true`. There is no configurable cancel
494
- effect. A recorder that cannot confirm completion must not offer Cancel; it can offer Stop instead.
495
- Cancel never means cancelling an in-flight start request or deleting arbitrary past recordings.
496
- Stop can be applied only after finalization/retention succeeds; Cancel only after both cessation
497
- and completion of this recording's audio succeed under the recorder's storage contract.
498
- Partial success -- capture stopped but the storage outcome unknown, a start the recorder cannot
499
- vouch for either way -- is a settled `failed`, under the code `omni.recording-unsettled`: the
500
- command did not do what it promised, and the state the provider publishes with it is whatever
501
- capture state is actually known. It is never `applied`, and never an unsettled promise the agent
502
- application would be left to resync, since the provider knows exactly what it could not settle.
503
- There is no request identity on a recording command: the outcome is the state, and what the
504
- agent sees is the state. Retention is not a promise of sample-perfect audio.
505
-
506
- Provider commands go exclusively to `Connection.execute`; agent application commands go exclusively to
507
- `HostRecording.execute`. Both include the assignment and observation identities; all
508
- non-start commands identify the particular recording. IDs are opaque and scoped by provider login,
509
- task and recorder owner. They are never inferred from filenames or current agent identity. A
510
- recording ID survives pause/resume and reassignment only where the same recorder confirms continuity;
511
- commands always name the current assignment. A later start gets a different ID. Reconnect does not
512
- create a new recording or fresh evidence. After lost continuity, use unknown until reconciled.
513
- There is no automatic restart, hand-over to another recorder, or stop on task hold/disconnect.
514
- Task removal does not prove recording stopped: outstanding agent application recorders remain tracked by the
515
- agent application until its executor reconciles/finishes them, with visible unresolved cleanup failures.
516
-
517
- Only start/resume require an in-progress or paused task with started audio. Pause/stop/cancel may
518
- also finish an independently observed recorder while the task is completing. A pending task may
519
- show active recording, but agent controls wait until interaction begins. The two recording paths may
520
- both be active. A command to either path has no implied effect on the other.
521
-
522
- Each confirmed observation has a fresh opaque observation identity, a canonical UTC millisecond
523
- observation instant and an exclusive expiry. These times describe current evidence, never historical
524
- capture boundaries. A trusted observer-domain current time must satisfy observedAt <= now < validUntil.
525
- Use `effectiveRecordingState` with that explicitly trusted time; an unavailable clock yields unknown.
526
- Receipt, replay and task publication never extend freshness. Clock discontinuity invalidates evidence;
527
- consumers using the explicit recording-expiry clock-estimation exception must invalidate that
528
- estimate and use monotonic aging so clock rollback cannot
529
- revive expired evidence. The executor compares observation identity and recording identity against
530
- its latest state atomically before I/O. An intervening observation or assignment change refuses the
531
- stale command; it never acts on a replacement recorder. Providers lacking trustworthy current-state
532
- identity or observation time publish unknown and offer no issuable controls.
533
-
534
- Use `validateTask` and `validateTaskCommand` for published shape and static permissions.
535
- Immediately before dispatch, additionally use `validateRecordingRequest` against the full current
536
- task and explicit observer-domain time. For agent application commands also supply the current agent application declaration,
537
- full agent application report and softphone context. The same checks must run at the executing boundary;
538
- client validation alone is not authorization. The executor serializes operations on each recorder
539
- and compares `observationId` and `recordingId` against its latest evidence before acting: a
540
- command against superseded evidence is refused, and nothing is retried on the executor's own
541
- account.
542
-
543
- Keep command progress in agent application UI separately from authoritative state. Applied means the requested
544
- recording transition and any retain/discard effect actually completed; publish the confirming task or agent application report before
545
- resolving applied. Failed means confirmed no effect. Rejected promise means outcome unknown:
546
- show the failure, reconcile that path and do not automatically retry or pretend inactive. Authoritative
547
- updates remain full current task/agent application views, not replayed provider events. These current-state fields
548
- create no interaction-history entries and infer no actors, durations or historical capture boundaries.
549
-
550
- Migration is explicit: replace the old true recording capability with per-path action policies,
551
- replace untargeted commands with scoped commands, and retain protocol version 1. During pre-release
552
- development, hosts and adapters must align their recording contracts; negotiation does not detect
553
- differences between package revisions that share that protocol number. No legacy-shape fallback is provided.
554
- This change does not publish a package or enable recording in any existing agent application/provider by itself.
555
-
556
- `validateRecordingOutcome` checks confirming observations against recording-specific applied/failed
557
- semantics, including pause/resume identity and rejection of a dialling result. It cannot prove audio
558
- retention/completion or source truth from a status flag; those remain executor obligations.
559
-
560
- The storage place named on the task is bound when start actually creates a recording. Existing recordings
561
- keep that binding through pause/resume/stop/cancel; a later policy cannot redirect their stored audio.
562
- The executor rejects a mismatched storage place rather than moving or discarding another binding.
563
- Action permission does not authorize unattended invocation: agent application controls require the agent's explicit
564
- act, and provider authorization remains enforced at its authenticated command boundary.
565
-
566
-
567
- Recording dispatch must receive the same known task-validation context as the published task.
568
- Pass organisation levels and other known restrictions through the optional fourth argument of
569
- `validateTaskCommand`, and through `taskContext` in `validateRecordingRequest`. This keeps a valid
570
- custom organisation lock from being rejected against the default ladder, and prevents known
571
- capability restrictions from disappearing at dispatch. The standalone `validateRecordingCommandState`
572
- requires an affirmative permission; false, malformed permission declarations and unknown actions grant nothing.
466
+ no recording controls. **Each recorder publishes its own recordings, as a list, whenever one
467
+ changes.** The provider's are the task's `recordings`, restated whole on every publication of the task
468
+ and on every snapshot. The agent application's are on its `HostReport.recordings`, one entry per
469
+ assignment, never on a provider's task. A provider task update cannot replace the agent
470
+ application's recordings, and a command to one recorder has no effect on the other.
471
+
472
+ ```json
473
+ "recordings": [
474
+ { "id": "rec-7", "follows": "party", "status": "recording" },
475
+ { "id": "rec-8", "follows": "agent", "status": "paused", "reason": "sensitive-details" },
476
+ { "id": "rec-3", "follows": "party", "status": "not-recording" }
477
+ ]
478
+ ```
573
479
 
480
+ **A recording follows one side of the call.** `follows` says whose channel it captures: the
481
+ `party`'s, or the `agent`'s own. The two are separate captures on most platforms, and the agent
482
+ computer needs to know which: a recording that follows the party keeps capturing hold music
483
+ while the agent talks to a colleague, and only one that follows the agent says whether the
484
+ agent's own voice is on it. Each side has at most one recording that is, or may be, capturing at
485
+ once -- `starting`, `recording`, `paused` or `unknown` (`recording.follows.live`) -- and any number
486
+ that have finished.
487
+
488
+ **Nothing expires.** A recording stands as its recorder last stated it until the recorder says
489
+ otherwise. There is no observation time, no validity window and no clock to compare: everything
490
+ the agent computer shows comes from the wire, and a recording mark that went dark when a recorder
491
+ check ran late, then came back, was a fault reported from the floor. The recorder publishes on
492
+ change and restates the whole list on connect and resume.
493
+
494
+ | Status | Meaning |
495
+ | --- | --- |
496
+ | `starting` | Asked for and not yet confirmed by the recorder. |
497
+ | `recording` | Capturing. A pause or a stop still in flight stays `recording` until the recorder confirms it: telling the agent they are not recorded when they may be is the one thing this must never say. |
498
+ | `paused` | Open but not capturing, with a `reason`: `requested`, someone asked for it, or `sensitive-details`, the platform paused it while card or account details are taken. |
499
+ | `not-recording` | Finished, or never started on that side; captured audio is kept or discarded as its stop or cancel said. |
500
+ | `unknown` | The recorder cannot say, with a `reason`: `recorder-unreachable`, or `unsettled`, a command whose effect it could not establish either way. Never shown as stopped. |
501
+
502
+ A mark that is not lit says why: `paused` and `unknown` carry a reason from those closed lists, and
503
+ nothing else carries one (`recording.reason`, `.reason.unexpected`). Task hold, task pause and
504
+ recording pause are independent.
505
+
506
+ **Permission is per task.** The policy is `Task.capabilities.recording`, with independently
507
+ optional provider and agent application action sets. The outer capability may be locked. Absence
508
+ grants no permission, and a recording may exist without any permission. A provider may offer only
509
+ stop for a recording started automatically, or withdraw a control on a later task update. There is
510
+ no global recording mode and no automatic start from a capability declaration. Agent application
511
+ support is declared on `ConnectContext.host.recording`, not the provider-owned manifest. The task
512
+ names one of the storage places the agent application provisioned, by `storageId`; an unknown
513
+ storage place or an unsupported action is refused visibly, never redirected to provider recording
514
+ or a default upload location. Initial agent application support requires voice softphone audio and
515
+ capture of both local and remote audio; an agent application unable to capture either must refuse
516
+ start/resume. This package declares that contract; it supplies no recorder, audio mixing, storage
517
+ or upload.
518
+
519
+ **A command names what it acts on.** A start names the side, `follows`; every other action names
520
+ the recording by its `recordingId`. The recorder mints the id when it accepts the start, before
521
+ the recorder has answered, publishes the recording `starting` under it, and keeps it through pause
522
+ and resume; a later start is a new recording with a new id. The recorder serializes the operations
523
+ on each recording and refuses an action the recording no longer stands for: a pause on a recording
524
+ that is not `recording`, a stop on one already `not-recording`, a start on a side already
525
+ capturing, a recording the task does not carry (`recording.command.state`,
526
+ `.command.recordingId`). Nothing is retried on the recorder's own account.
527
+
528
+ | Action | The recording before | Confirmed outcome |
529
+ | --- | --- | --- |
530
+ | start | none live on that side | a new recording on that side, `recording` |
531
+ | pause | `recording` | the same recording, `paused`, captured audio kept |
532
+ | resume | `paused` | the same recording, `recording` |
533
+ | stop | `recording` or `paused` | the same recording, `not-recording`; captured audio finished and kept |
534
+ | cancel | `recording` or `paused` | the same recording, `not-recording`; captured audio discarded |
535
+
536
+ Stop finishes the recording and retains captured audio. Cancel abandons it and discards captured
537
+ audio; the task policy offers it with `cancel: true`, and there is no configurable cancel effect.
538
+ A recorder that cannot confirm completion must not offer Cancel; it can offer Stop instead. Cancel
539
+ never means cancelling an in-flight start or deleting past recordings. Stop is applied only after
540
+ finalization and retention succeed; Cancel only after both cessation and discarding succeed.
541
+ **Applied means confirmed**: the recorder publishes the confirming list before it answers
542
+ `applied`. Failed means confirmed no effect. Partial success -- capture stopped but the storage
543
+ outcome unknown, a start the recorder cannot vouch for either way -- is a settled `failed` under
544
+ `omni.recording-unsettled`, and the recording is published `unknown` with the reason `unsettled`.
545
+ A rejected promise means the outcome is unknown: show the failure, reconcile, and do not retry or
546
+ pretend `not-recording`. There is no request identity on a recording command: the outcome is the
547
+ recording, and what the agent sees is the recording. Retention is not a promise of sample-perfect
548
+ audio.
549
+
550
+ Provider commands go only to `Connection.execute`; agent application commands go only to
551
+ `HostRecording.execute`. Both name the assignment. Ids are opaque and scoped by provider login,
552
+ task and recorder; they are never inferred from filenames or the current agent. Reconnect creates
553
+ no recording; the recorder restates its list. There is no automatic restart, hand-over to another
554
+ recorder, or stop on task hold or disconnect. Task removal does not prove recording stopped:
555
+ outstanding agent application recordings stay tracked by the agent application until its recorder
556
+ finishes them, with unresolved cleanup failures visible.
557
+
558
+ Only start and resume need an `in-progress` or `paused` task with started audio. Pause, stop and
559
+ cancel may also finish a recording while the task is `completing`. A pending task may show a
560
+ recording, but agent controls wait until interaction begins.
561
+
562
+ Use `validateTask` and `validateTaskCommand` for the published shape and static permissions.
563
+ Immediately before dispatch, also use `validateRecordingRequest` against the full current task;
564
+ for agent application commands supply the current declaration, full report and softphone context.
565
+ The same checks run at the recorder; client validation alone is not authorization.
566
+ `validateRecordings` checks one recorder's list, and `validateRecordingOutcome` checks an answer
567
+ against the lists published before and after it: an applied start shows a new recording on its side,
568
+ anything else the same recording in its new status, and a failed one nothing changed there. It
569
+ cannot prove retention or source truth from a status; those remain the recorder's obligations. The
570
+ storage place named on the task is bound when start creates a recording, and later policy cannot
571
+ redirect it.
572
+
573
+ Recording dispatch receives the same known task-validation context as the published task: pass
574
+ organisation levels and other known restrictions through the optional fourth argument of
575
+ `validateTaskCommand`, and through `taskContext` in `validateRecordingRequest`, so a valid custom
576
+ organisation lock is not rejected against the default ladder. Migration is explicit: the former
577
+ `recording` state with its observation and validity window is refused on a task
578
+ (`recording.task.renamed`), and a command carries no observation id. Protocol version 1 stays;
579
+ there is no legacy-shape fallback.
574
580
 
575
581
  ### Agent application recording announcements to the caller
576
582
 
@@ -590,7 +596,7 @@ cancel, they hear that recording stopped. If pause/resume is supported, say paus
590
596
  pause is not presented as a finished recording. Announcements follow actual capture transitions,
591
597
  not button clicks, accepted requests or task-policy changes. When attaching to an already-active
592
598
  agent application recording, announce that recording is active, without inventing its original start time.
593
- Repeated observations of the same state do not repeat announcements. Unknown or expired evidence
599
+ Restating the same status does not repeat an announcement. A recording that is `unknown`
594
600
  must never produce a stopped announcement.
595
601
 
596
602
  A local sound, UI indicator, screen-reader message to the agent, or sound leaking through the
package/guide.md CHANGED
@@ -70,7 +70,7 @@ state use `status`; name the object when discussing them to avoid ambiguity.
70
70
  | `phase` | A task | `pending`, `confirmed`, `preview`, `in-progress`, `paused`, `completing` |
71
71
  | `audio` | A task's audio | `started`, `ended` |
72
72
  | `transport` | A connection | `connecting`, `active`, `error` |
73
- | `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired` |
73
+ | `status` | An authentication session | `signed-out`, `authenticating`, `authenticated`, `refreshing`, `expired`, `terminated` |
74
74
  | `status` | The agent’s break lifecycle | `not-requested`, `awaiting-approval`, `granted`, `starting-after-task`, `on-break` |
75
75
  | `availability` | A team member | `ready`, `on-task`, `on-break`, `elsewhere`, `signed-out` |
76
76
 
@@ -83,7 +83,7 @@ user IDs, returned user details and directory requirements are unchanged.
83
83
 
84
84
  ### `OMNI_PROTOCOL_VERSION`
85
85
 
86
- The exact protocol version implemented by this package. The current value remains `1` during pre-release development. Recording contract changes do not introduce a new protocol version. Hosts and adapters adopt the current declarations together; this does not provide compatibility mapping for older recording shapes.
86
+ The exact protocol version implemented by this package. The current value remains `1` during pre-release development. Recording contract changes do not introduce a new protocol version. Hosts and adapters adopt the current declarations together; there is no compatibility mapping for older recording shapes.
87
87
 
88
88
  ### `Manifest.supportedProtocolVersions`
89
89
 
@@ -301,7 +301,8 @@ type AuthenticationState =
301
301
  | { status: "authenticating" }
302
302
  | { status: "authenticated"; identity: User; capabilities: UserCapabilities; expiresAt?: IsoTimestamp }
303
303
  | { status: "refreshing"; identity: User; capabilities: UserCapabilities }
304
- | { status: "expired"; identity?: User; failure?: AuthenticationFailure };
304
+ | { status: "expired"; identity?: User; failure?: AuthenticationFailure }
305
+ | { status: "terminated"; by: UserId | "provider"; identity?: User; failure: AuthenticationFailure & { retryable: false } };
305
306
 
306
307
  type AuthenticationFailure = {
307
308
  code: string;
@@ -342,23 +343,22 @@ interface TaskRecordingPolicy {
342
343
  provider?: RecordingActions;
343
344
  host?: RecordingActions & { storageId: string };
344
345
  }
345
- type RecordingState =
346
- | { status: "unknown"; observationId?: never; observedAt?: never; validUntil?: never; recordingId?: never }
347
- | ({ observationId: string; observedAt: IsoTimestamp; validUntil: IsoTimestamp } & (
348
- | { status: "inactive"; recordingId?: never }
349
- | { status: "active" | "paused"; recordingId: string }
350
- ));
351
- type RecordingCommand = {
352
- type: "recording";
353
- source: RecordingSource;
354
- observationId: string;
355
- } & (
356
- | { action: "start"; recordingId?: never }
357
- | { action: "pause" | "resume" | "stop" | "cancel"; recordingId: string }
346
+ type RecordingSide = "party" | "agent";
347
+ type RecordingStatus = "starting" | "recording" | "paused" | "not-recording" | "unknown";
348
+ type RecordingPausedReason = "requested" | "sensitive-details";
349
+ type RecordingUnknownReason = "recorder-unreachable" | "unsettled";
350
+ type Recording = { id: string; follows: RecordingSide } & (
351
+ | { status: "starting" | "recording" | "not-recording"; reason?: never }
352
+ | { status: "paused"; reason: RecordingPausedReason }
353
+ | { status: "unknown"; reason: RecordingUnknownReason }
354
+ );
355
+ type RecordingCommand = { type: "recording"; source: RecordingSource } & (
356
+ | { action: "start"; follows: RecordingSide; recordingId?: never }
357
+ | { action: "pause" | "resume" | "stop" | "cancel"; recordingId: string; follows?: never }
358
358
  );
359
359
  interface HostRecordingReport {
360
360
  assignmentId: AssignmentId;
361
- state: RecordingState;
361
+ recordings: Recording[];
362
362
  }
363
363
  interface HostRecording {
364
364
  announcesToCaller?: true;
@@ -693,7 +693,8 @@ type HistoryStep =
693
693
  | "muted"
694
694
  | "transferred"
695
695
  | "conferenced"
696
- | "unanswered";
696
+ | "unanswered"
697
+ | "callback-requested";
697
698
 
698
699
  type TaskHistory = {
699
700
  steps: TaskHistoryStep[];
@@ -776,8 +777,8 @@ type Task<C extends Channel = Channel> = {
776
777
  history?: TaskHistory;
777
778
  } & TaskCompletion & (
778
779
  C extends "voice"
779
- ? { recording?: { provider?: RecordingState }; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; audio?: TaskAudioState }
780
- : { recording?: never; onCall?: never; leadAssist?: never; takenOver?: never; audio?: never }
780
+ ? { recordings?: Recording[]; onCall?: OnCall[]; leadAssist?: TaskLeadAssist; takenOver?: TaskTakenOver; audio?: TaskAudioState }
781
+ : { recordings?: never; onCall?: never; leadAssist?: never; takenOver?: never; audio?: never }
781
782
  );
782
783
 
783
784
  type PreviewDeadline = "provider-dials" | "host-dials" | "waits";
@@ -1344,6 +1345,7 @@ const OMNI_FAILURE_CODES = [
1344
1345
  "omni.unavailable",
1345
1346
  "omni.break-already-committed",
1346
1347
  "omni.recording-unsettled",
1348
+ "omni.on-hold",
1347
1349
  "omni.break-forced-by-provider",
1348
1350
  ] as const;
1349
1351
  type OmniFailureCode = (typeof OMNI_FAILURE_CODES)[number];
@@ -1991,11 +1993,12 @@ reports later changes.
1991
1993
 
1992
1994
  | Status | Contract |
1993
1995
  | --- | --- |
1994
- | `signed-out` | No usable provider session exists. |
1996
+ | `signed-out` | No usable provider session exists, because the agent signed out on this agent computer, or never signed in. Only the agent's own Sign out here leads to it. |
1995
1997
  | `authenticating` | An interactive `browser-sso` or `credentials` flow is active. |
1996
1998
  | `authenticated` | A usable session exists. Includes the provider identity, the login's `capabilities`, and optional token expiry time. |
1997
1999
  | `refreshing` | The adapter is refreshing its session. Existing provider identity and capabilities remain available, unchanged: a change to either is published as `authenticated`. |
1998
- | `expired` | The session cannot currently be used. It may include an identity and typed failure. |
2000
+ | `expired` | The session's time ran out, or its refresh is failing. It may include an identity and typed failure. The agent signs in again on the same login, and the workspace stays. |
2001
+ | `terminated` | The session was ended from outside this agent computer: by a supervisor, by the agent on another screen, or by the platform itself. `by` says who, a person by `UserId` or `provider` where no person was behind it (`authentication.terminated.by`), and `failure` says why and is never retryable (`authentication.terminated.failure`, `.retryable`). It is never reported as `expired`, which would hide who ended it, nor as `signed-out`, which is the agent's own act. |
1999
2002
 
2000
2003
  Omni calls `connect()` only after authentication reaches `authenticated`. Token refresh remains
2001
2004
  adapter-owned; the adapter publishes `refreshing`, followed by `authenticated` or `expired`.
@@ -2137,16 +2140,50 @@ authorization codes, tokens, or provider responses containing secrets.
2137
2140
 
2138
2141
  ### Sign-out
2139
2142
 
2140
- `signOut()` revokes or invalidates the provider session where supported, deletes stored
2141
- session secrets, and moves state to `signed-out`.
2142
- `close()` stops authentication-state observation but does not sign the agent out.
2143
+ `signOut()` is the agent's own Sign out on this agent computer. It tells the provider where
2144
+ supported, revoking or invalidating the session, then **deletes the stored session secrets
2145
+ whatever happened**, and moves state to `signed-out`. Telling the provider may be retried within a
2146
+ short bound the adapter states, and then given up: a provider that cannot be reached never keeps a
2147
+ token on the agent's machine. If it was not told, `signOut()` still deletes and still reaches
2148
+ `signed-out`, and answers `failed`, so the agent application can show that a session may remain at
2149
+ the provider. `close()` stops authentication-state observation but does not sign the agent out.
2150
+
2151
+ **The final moments are the agent application's to finish.** Its own steps before signing out --
2152
+ stopping capacity, putting tasks down -- may be retried within a short bound it states, and then
2153
+ given up; a step that failed never skips `signOut()`. A desk that skipped the adapter's sign-out
2154
+ because its own retirement failed left the token behind, and the next reload restored the session.
2155
+
2156
+ ### How a session's secrets end
2157
+
2158
+ A token that can no longer start a session does not outlive it. The adapter deletes the stored
2159
+ secrets **before** it publishes the ending, so that nothing is left for a reload to restore:
2160
+
2161
+ | Ending | Secrets | The provider |
2162
+ | --- | --- | --- |
2163
+ | `signed-out` | Deleted, whether or not the provider was told. | Told where supported, as above. |
2164
+ | `terminated` | Deleted. | Never told: the session is already over, and a sign-out sent now could end the agent's newer session somewhere else. |
2165
+ | `expired` with a failure that says `retryable: true` | Kept, for the retry: a network outage does not sign out a floor. | Retried by the adapter. |
2166
+ | `expired` otherwise | Deleted: the provider refused the token, or its time ran out. | Not told. |
2167
+
2168
+ **The agent application makes sure after the adapter.** Once a session is `signed-out`,
2169
+ `terminated`, or `expired` with anything but a retryable failure, the agent application clears that
2170
+ provider's partition of the store, and a sign-in after it always starts a fresh flow and never
2171
+ resumes a stored token. The adapter deletes; the agent application makes sure. `testAdapter` holds
2172
+ the adapter's half: when a published state ends the session, the store it handed the adapter must
2173
+ already be empty (`authentication.secrets.retained`).
2143
2174
 
2144
2175
  ### Secure-storage boundary
2145
2176
 
2146
- Omni provides an OS-backed `SecretStore` scoped to the provider manifest ID. It exposes only
2147
- `get`, `set`, and `delete`; adapters cannot enumerate another provider's secrets. Adapters may store
2148
- refresh tokens or equivalent session material, but never raw submitted credentials. Secrets must
2149
- not appear in manifests, logs, events, snapshots, task attributes, errors, or browser storage.
2177
+ The agent application provides the `SecretStore`, scoped to the provider manifest ID. It exposes
2178
+ only `get`, `set`, and `delete`; adapters cannot enumerate another provider's secrets. **What backs
2179
+ it is the agent application's, and depends on what it is**: a native agent application uses the
2180
+ operating system's keychain; a browser agent application has none, and keeps the store in the
2181
+ tab's session storage, which survives a reload and ends with the tab. A browser store is readable
2182
+ by any script running on its page, so a browser agent application is only as safe as its own page.
2183
+
2184
+ Adapters may store refresh tokens or equivalent session material, but never raw submitted
2185
+ credentials, and they put a secret nowhere but the `SecretStore`: never in manifests, logs,
2186
+ events, snapshots, task attributes, errors, the login's `store`, or browser storage of their own.
2150
2187
 
2151
2188
  ## Connecting to a provider
2152
2189
 
@@ -2379,8 +2416,6 @@ The current exceptions and their reasons are:
2379
2416
  | --- | --- |
2380
2417
  | Agent application display of provider deadlines (`expiresInSeconds`, `previewEndsInSeconds`, `wrapEndsInSeconds`) | Each is seconds from the publication that carried it, counted down on the agent computer from receipt: the provider's own arithmetic, so no clock is compared and the transport delay is the only error. The countdown estimates the display only; it does not establish that the provider acted. Without usable time, show timing uncertainty. |
2381
2418
  | Agent application-triggered preview end | The agent computer counts `previewEndsInSeconds` down from receipt and issues `dial` when it reaches zero: the provider owns the deadline and the agent computer owns the trigger. It does not dial before the countdown has run out, and a reload restarts it from the next publication. |
2382
- | 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. |
2383
- | 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. |
2384
2419
  | 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. |
2385
2420
  | Agent application-provided provider-clock estimate | Best-effort ISO time may be supplied by the agent application for optional comparisons; it is not a source observation or accuracy guarantee. The reason is that the agent application does not own the provider clock. |
2386
2421
  | Provider receipt timestamps | The provider may use its own receipt instant for final records of receipt/processing because agent application timestamps are untrusted advisory input. Receipt must not be presented as an earlier action or capture boundary. |
@@ -2488,14 +2523,13 @@ A provider need not trust or adopt any agent application-supplied timestamp. It
2488
2523
  message using its own clock at receipt and use that for its own processing/accounting. The reason
2489
2524
  for this exception is that agent application clocks and agent application estimates are not authoritative at the provider.
2490
2525
  Receipt time must be described as receipt/observation time, not relabeled as the original agent application
2491
- action, recorder capture boundary, or proof an operation applied. An existing field with a
2526
+ action, a recording's start or stop, or proof an operation applied. An existing field with a
2492
2527
  specific occurrence meaning still requires that evidence; this option does not silently redefine
2493
2528
  it. Agent application-only instants remain advisory input; the provider owns its authoritative publication.
2494
2529
 
2495
2530
  This agent application estimate is another explicit timestamp exception: it helps optional displays and
2496
2531
  provider-clock comparisons when the agent application lacks that clock directly. It is not a source occurrence,
2497
- may not replace event/history timestamps, and alone cannot authorize deadline actions or prove
2498
- recording freshness. Uses requiring a trustworthy bound still need independently established
2532
+ may not replace event/history timestamps, and alone cannot authorize deadline actions. Uses requiring a trustworthy bound still need independently established
2499
2533
  clock accuracy/drift assumptions. It neither synchronizes the operating-system clock nor changes
2500
2534
  ISO timestamps already published by the provider.
2501
2535
 
@@ -2677,7 +2711,9 @@ snapshots until it ends.
2677
2711
  with `task-ended` and an outcome, whatever became of the call: answered and completed, declined,
2678
2712
  withdrawn, abandoned in the ring, lapsed, taken over. An offer that is simply never mentioned
2679
2713
  again leaves the agent computer holding a task nobody will close, and the test names it
2680
- (`stream.taskOffered.unended`).
2714
+ (`stream.taskOffered.unended`). **Every dial is owed its outcome** the same way: a dial the test
2715
+ placed and the provider answered `dialling` with no `dial-outcome` by the end of the run is named
2716
+ (`stream.dialOutcome.missing`).
2681
2717
 
2682
2718
  ### `task-updated`
2683
2719
 
@@ -3108,6 +3144,7 @@ cannot be established from TypeScript structure alone.
3108
3144
  | `TaskStream`, `BreakStream` | The cross-event models the harness applies after the connect snapshot, exported for an agent application that wants the same rules at its boundary: `seed(snapshot)`, then `apply(envelope)` returns the violations. |
3109
3145
  | `assertBreakFollowsItsRequests(envelopes, snapshot?)` | A break follows its requests: a commit's states only after a grant, never backwards, and a forced break arriving in effect with `forced`. The harness applies the same rules after the connect snapshot. |
3110
3146
  | `assertAudioFollowsTheTask(envelopes, snapshot?)` | The audio follows the task and never decides it: every task is introduced once, `task-audio-started` and `task-audio-ended` alternate on work that has begun, audio ends only where it arrived, and what follows the audio ending is `completing` or `task-ended`. The harness applies the same rules to every event after the connect snapshot (`stream.*`). A sequence with no audio satisfies it by never testing it — pair it with the assertion that the audio end is present. |
3147
+ | `assertHistoryAgreesAcrossTasks(earlier, later)` | Two tasks of one call -- a hand-over, a take-over, a callback: the later restates every earlier step in order, with the same step, instant, person, dial and destination, and any duration already stated unchanged. Throws when either carries no history. |
3111
3148
  | `assertBreakAttemptProviders(candidates, asked)` | A break attempt asks every usable provider holding capacity, `refreshing` included, and nothing of a provider whose login is `expired`. |
3112
3149
  | `assertForcedBreakStopsTheRest(forcedOn, candidates, stopped)` | A forced break on one provider stops the agent everywhere else: capacity zero stated on every other usable provider holding capacity, nothing on the forcing one, nothing on a dead login. See **Forced breaks** in `guide/breaks.md`. |
3113
3150
  | `assertBreakBeginsAfterTask(steps)` | A break asked for on a task is committed as `starting-after-task` while work remains and reaches `on-break` only once nothing is outstanding — never beside a task, never later than the step that has none. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.116",
3
+ "version": "0.1.118",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",