@xema/omni-protocol 0.1.75 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -904,11 +904,14 @@ export type TaskOutcome = {
904
904
  } | {
905
905
  type: "transferred";
906
906
  destinationId?: string;
907
- } | {
907
+ }
908
+ /** Who called the work off: the agent declining, the provider withdrawing or re-routing, the party abandoning. */
909
+ | {
908
910
  type: "cancelled";
911
+ by: "agent" | "provider" | "party";
909
912
  reason?: string;
910
913
  }
911
- /** Only the phases in which somebody is still being waited on can expire. */
914
+ /** Only the phases in which somebody is still being waited on can expire; an offer that lapses at `allocationExpiresAt` names `pending`. */
912
915
  | {
913
916
  type: "expired";
914
917
  phase: "pending" | "confirmed" | "preview";
package/dist/testing.d.ts CHANGED
@@ -170,6 +170,15 @@ export declare class TaskStream {
170
170
  /** What a restated record lost of the one read before it: nothing, or the entries by step and instant. */
171
171
  private static lost;
172
172
  private static stated;
173
+ /**
174
+ * Takes a resync snapshot -- a `snapshot` event, or the read a reloaded client makes -- as the
175
+ * state now, and first holds it to what the stream knew. A snapshot replaces state; it does not
176
+ * get to forget it. A task it carries that the stream already held may not go backwards, lose an
177
+ * entry of its record, return to undetermined terms, or be at work without the audio the stream
178
+ * held up: media ends on task-media-ended and the call moves on, so a task still in-progress or
179
+ * paused without its media is a snapshot that lost state, not a call that ended.
180
+ */
181
+ resync(snapshot: unknown, at: string): ProtocolViolation[];
173
182
  /** Replaces what is known with a snapshot's tasks, as a snapshot replaces Omni's state. */
174
183
  seed(snapshot: unknown): void;
175
184
  /** Applies one envelope and returns what it may not say given what came before. */
package/dist/testing.js CHANGED
@@ -1043,7 +1043,52 @@ export class TaskStream {
1043
1043
  stages.set(entry.dialId, entry.stage);
1044
1044
  }
1045
1045
  }
1046
- return { phase: String(isRecord(task) ? task.phase : undefined), media, source: String(isRecord(task) ? task.capabilitySource : undefined), stages, record: TaskStream.record(task), allocation: String(isRecord(task) ? task.allocationId : undefined) };
1046
+ return { channel: String(isRecord(task) ? task.channel : undefined), completionMode: String(isRecord(task) ? task.completionMode : undefined),
1047
+ wrapAllowance: isRecord(task) && typeof task.wrapAllowance === "number" ? task.wrapAllowance : undefined,
1048
+ phase: String(isRecord(task) ? task.phase : undefined), media, source: String(isRecord(task) ? task.capabilitySource : undefined), stages, record: TaskStream.record(task), allocation: String(isRecord(task) ? task.allocationId : undefined) };
1049
+ }
1050
+ /**
1051
+ * Takes a resync snapshot -- a `snapshot` event, or the read a reloaded client makes -- as the
1052
+ * state now, and first holds it to what the stream knew. A snapshot replaces state; it does not
1053
+ * get to forget it. A task it carries that the stream already held may not go backwards, lose an
1054
+ * entry of its record, return to undetermined terms, or be at work without the audio the stream
1055
+ * held up: media ends on task-media-ended and the call moves on, so a task still in-progress or
1056
+ * paused without its media is a snapshot that lost state, not a call that ended.
1057
+ */
1058
+ resync(snapshot, at) {
1059
+ const found = [];
1060
+ const refuse = (rule, where, message) => found.push({ rule, path: where, message });
1061
+ ruleEvaluated("stream.snapshot.capabilitySource", "stream.snapshot.handlingHistory", "stream.snapshot.phase", "stream.snapshot.media");
1062
+ if (isRecord(snapshot) && Array.isArray(snapshot.tasks)) {
1063
+ snapshot.tasks.forEach((task, index) => {
1064
+ if (!isRecord(task) || typeof task.id !== "string")
1065
+ return;
1066
+ const was = this.tasks.get(task.id);
1067
+ if (was === undefined || was.allocation !== String(task.allocationId))
1068
+ return;
1069
+ if ((was.source === "queue" || was.source === "ungoverned") && task.capabilitySource === "undetermined") {
1070
+ refuse("stream.snapshot.capabilitySource", `${at}.tasks[${index}].capabilitySource`, `${task.id} was published under ${was.source} terms and the snapshot says undetermined: terms once read stay read`);
1071
+ }
1072
+ // A record once read is not unread: a resync restates it whole, or with more, never with less.
1073
+ const lost = TaskStream.lost(was.record, TaskStream.record(task));
1074
+ if (lost.length > 0) {
1075
+ refuse("stream.snapshot.handlingHistory", `${at}.tasks[${index}].handlingHistory`, `${task.id}'s record lost ${lost.join(", ")} on the snapshot: an entry read by the host stays in the record until the task ends`);
1076
+ }
1077
+ const to = String(task.phase);
1078
+ const reachable = REACHABLE_PHASES[was.phase];
1079
+ if (reachable !== undefined && TASK_PHASES_ORDERED.includes(to) && !reachable.has(to)) {
1080
+ const connectingBack = was.phase === "completing" && TaskStream.partyDialled(task) && (to === "in-progress" || to === "paused");
1081
+ if (!connectingBack) {
1082
+ refuse("stream.snapshot.phase", `${at}.tasks[${index}].phase`, `${task.id} was ${was.phase} and the snapshot says ${to}: a task does not go backwards, on an update or on a resync`);
1083
+ }
1084
+ }
1085
+ if (was.media === "started" && (to === "in-progress" || to === "paused") && task.media !== "started") {
1086
+ refuse("stream.snapshot.media", `${at}.tasks[${index}].media`, `${task.id}'s audio was up and the snapshot carries it ${to} without it: media ends on task-media-ended and the call moves on, so a snapshot that forgets the audio lost state`);
1087
+ }
1088
+ });
1089
+ }
1090
+ this.seed(snapshot);
1091
+ return found;
1047
1092
  }
1048
1093
  /** Replaces what is known with a snapshot's tasks, as a snapshot replaces Omni's state. */
1049
1094
  seed(snapshot) {
@@ -1071,24 +1116,7 @@ export class TaskStream {
1071
1116
  const known = id === undefined ? undefined : this.tasks.get(id);
1072
1117
  switch (event.type) {
1073
1118
  case "snapshot":
1074
- ruleEvaluated("stream.snapshot.capabilitySource", "stream.snapshot.handlingHistory");
1075
- // A snapshot replaces what is known, and still may not say a task lost terms it had read.
1076
- if (isRecord(event.snapshot) && Array.isArray(event.snapshot.tasks)) {
1077
- event.snapshot.tasks.forEach((task, index) => {
1078
- if (!isRecord(task) || typeof task.id !== "string")
1079
- return;
1080
- const was = this.tasks.get(task.id);
1081
- if (was !== undefined && (was.source === "queue" || was.source === "ungoverned") && task.capabilitySource === "undetermined") {
1082
- refuse("stream.snapshot.capabilitySource", `${at}.snapshot.tasks[${index}].capabilitySource`, `${task.id} was published under ${was.source} terms and the snapshot says undetermined: terms once read stay read`);
1083
- }
1084
- // A record once read is not unread: a resync restates it whole, or with more, never with less.
1085
- const lost = TaskStream.lost(was?.record, TaskStream.record(task));
1086
- if (lost.length > 0) {
1087
- refuse("stream.snapshot.handlingHistory", `${at}.snapshot.tasks[${index}].handlingHistory`, `${task.id}'s record lost ${lost.join(", ")} on the snapshot: an entry read by the host stays in the record until the task ends`);
1088
- }
1089
- });
1090
- }
1091
- this.seed(event.snapshot);
1119
+ found.push(...this.resync(event.snapshot, `${at}.snapshot`));
1092
1120
  break;
1093
1121
  case "task-offered": {
1094
1122
  ruleEvaluated("stream.taskOffered.duplicate", "stream.taskOffered.allocation");
@@ -1231,6 +1259,15 @@ export class TaskStream {
1231
1259
  // A late ending for a life that is over must not end the life that is open under the same id.
1232
1260
  if (!this.namesTheOpenLife(event, known, at, refuse))
1233
1261
  break;
1262
+ // Off voice there is no media event, so the update that moves a task to completing is the
1263
+ // provider's word that handling ended and the moment the wrap allowance starts. A provider
1264
+ // that completes the task itself with an allowance to run has to have started the clock:
1265
+ // completed from in-progress, the allowance it stated was never given.
1266
+ ruleEvaluated("stream.taskEnded.unwrapped");
1267
+ if (known.channel !== "voice" && known.completionMode === "provider-automatic" && known.wrapAllowance !== undefined && known.wrapAllowance > 0
1268
+ && known.phase !== "completing" && isRecord(event.outcome) && event.outcome.type === "completed") {
1269
+ refuse("stream.taskEnded.unwrapped", `${at}.outcome`, `${id} was completed from ${known.phase} with a wrap allowance of ${known.wrapAllowance}s under provider-automatic: off voice, completing is the provider's word that handling ended and the allowance's start, and it was never published`);
1270
+ }
1234
1271
  this.endedAllocations.add(known.allocation);
1235
1272
  this.tasks.delete(id);
1236
1273
  break;
@@ -1335,9 +1372,11 @@ async function driveOneCall(drive) {
1335
1372
  drive.handOver.up(session, second);
1336
1373
  drive.connection = second;
1337
1374
  const snapshot = await second.snapshot();
1338
- // The second adapter's snapshot is held to everything a first one is, and the streams take it as the state now.
1375
+ // The second adapter's snapshot is held to everything a first one is, and the streams take it
1376
+ // as the state now the way they take any resync: held to what they knew, then replaced. A reload
1377
+ // is a place the stream's rules keep working, not one where they all stop.
1339
1378
  found.push(...validateSnapshot(snapshot, again.manifest, "drive.reload.snapshot", drive.reader()));
1340
- drive.streams.stream.seed(snapshot);
1379
+ found.push(...drive.streams.stream.resync(snapshot, "drive.reload.snapshot"));
1341
1380
  drive.streams.breaks.seed(snapshot);
1342
1381
  const carried = isRecord(snapshot) && Array.isArray(snapshot.tasks)
1343
1382
  ? snapshot.tasks.find(task => isRecord(task) && task.id === taskId) : undefined;
@@ -89,6 +89,7 @@ const PHONES = membersOf({ softphone: true, deskPhone: true });
89
89
  const TEAM_CAPABILITIES = membersOf({ breakControl: true, leadAssistControl: true, policyControl: true, monitorControl: true });
90
90
  const MONITOR_MODES = membersOf({ monitor: true, whisper: true, barge: true });
91
91
  const COMPLETED_BY = membersOf({ agent: true, provider: true });
92
+ const CANCELLED_BY = membersOf({ agent: true, provider: true, party: true });
92
93
  const EXPIRABLE_PHASES = membersOf({
93
94
  pending: true, confirmed: true, preview: true,
94
95
  });
@@ -994,6 +995,12 @@ function validateMonitoring(value, channel, path, into) {
994
995
  into.oneOf(value.mode, MONITOR_MODES, "task.monitoring.mode", `${path}.mode`);
995
996
  into.timestamp(value.since, "task.monitoring.since", `${path}.since`);
996
997
  }
998
+ /** Whether a task is the agent's own doing: a dial the host placed on its onCall, a lead's join, a lead's monitor. */
999
+ function originatedByTheAgent(task) {
1000
+ if (Array.isArray(task.onCall) && task.onCall.some(entry => isPlainObject(entry) && typeof entry.dialId === "string"))
1001
+ return true;
1002
+ return task.assisting !== undefined || task.monitoring !== undefined;
1003
+ }
997
1004
  export function validateTask(task, context, path = "task") {
998
1005
  const into = new Collector();
999
1006
  validateTaskInto(task, context, path, into);
@@ -1032,15 +1039,27 @@ function validateTaskInto(task, context, path, into) {
1032
1039
  }
1033
1040
  // Acceptance is the offer's word, carried on the pending task so a snapshot can say it: it
1034
1041
  // travels exactly when Omni said tasks may be auto-accepted, and only while the task is pending.
1042
+ // Work the agent originated -- a dial or connect-back, carrying the host's dialId on onCall; a
1043
+ // lead's join, carrying assisting; a monitor, carrying monitoring -- was accepted by the command
1044
+ // that created it, and says so whatever the provisioning: the desk shows no Accept for a call
1045
+ // the agent placed. The provisioning governs work the queue routes, and nothing else.
1046
+ const originated = task.phase === "pending" && originatedByTheAgent(task);
1047
+ ruleEvaluated("task.acceptance.originated");
1035
1048
  if (task.acceptance !== undefined) {
1036
1049
  into.oneOf(task.acceptance, ACCEPTANCE_MODES, "task.acceptance", `${path}.acceptance`);
1037
1050
  if (task.phase !== "pending") {
1038
1051
  into.add("task.acceptance.unexpected", `${path}.acceptance`, "acceptance is an offer's word; a task past pending has been accepted");
1039
1052
  }
1053
+ else if (originated) {
1054
+ into.require(task.acceptance === "automatic", "task.acceptance.originated", `${path}.acceptance`, "work the agent originated -- a dial, a connect-back, a join, a monitor -- was accepted by the command that created it, and says automatic whatever the provisioning");
1055
+ }
1040
1056
  else if (context.autoAcceptTasks === false) {
1041
- into.add("task.acceptance.unexpected", `${path}.acceptance`, "autoAcceptTasks is off, so every task requires agent acceptance and a pending task carries no acceptance");
1057
+ into.add("task.acceptance.unexpected", `${path}.acceptance`, "autoAcceptTasks is off, so every task the queue routes requires agent acceptance and a pending task carries no acceptance");
1042
1058
  }
1043
1059
  }
1060
+ else if (originated) {
1061
+ into.add("task.acceptance.originated", `${path}.acceptance`, "work the agent originated -- a dial, a connect-back, a join, a monitor -- says acceptance: automatic whatever the provisioning: the command that created it accepted it");
1062
+ }
1044
1063
  else if (task.phase === "pending" && context.autoAcceptTasks === true) {
1045
1064
  into.add("task.acceptance.required", `${path}.acceptance`, "autoAcceptTasks is on, so a pending task states how it is accepted");
1046
1065
  }
@@ -1469,6 +1488,9 @@ function validateTaskOutcome(value, path, into) {
1469
1488
  }
1470
1489
  break;
1471
1490
  case "cancelled":
1491
+ // One word covered the agent declining, the provider withdrawing and the party abandoning the
1492
+ // ring; a record that cannot tell them apart is not a record. by says who, as completed does.
1493
+ into.oneOf(value.by, CANCELLED_BY, "event.taskEnded.outcome.cancelled.by", `${path}.by`);
1472
1494
  if (value.reason !== undefined) {
1473
1495
  into.filled(value.reason, "event.taskEnded.outcome.cancelled", `${path}.reason`, "a reason must not be empty when present");
1474
1496
  }
package/guide.md CHANGED
@@ -702,7 +702,7 @@ type AcceptanceMode =
702
702
  type TaskOutcome =
703
703
  | { type: "completed"; by: "agent" | "provider" }
704
704
  | { type: "transferred"; destinationId?: string }
705
- | { type: "cancelled"; reason?: string }
705
+ | { type: "cancelled"; by: "agent" | "provider" | "party"; reason?: string }
706
706
  | { type: "expired"; phase: "pending" | "confirmed" | "preview" }
707
707
  | { type: "left" }
708
708
  | { type: "failed"; failure: ProtocolFailure };
@@ -2170,8 +2170,8 @@ person's behalf for want of a word:
2170
2170
  | `consent` | The provider requires the person's explicit consent: Omni presents **Accept** and waits, whatever its own policy would have done. A host that declares `guarantees.personConsent` promises exactly this; a provider checks it before offering work only a person may take. |
2171
2171
  | `automatic` | Omni accepts immediately without agent interaction. |
2172
2172
 
2173
- When Omni sent `autoAcceptTasks: false`, the provider omits `acceptance` and every task
2174
- requires agent acceptance. The two are never confused on the wire: `consent` is always the
2173
+ When Omni sent `autoAcceptTasks: false`, the provider omits `acceptance` and every task the
2174
+ queue routes requires agent acceptance. The two are never confused on the wire: `consent` is always the
2175
2175
  provider's requirement, stated on a wire where Omni was willing to accept for the agent; Omni's own
2176
2176
  no-auto-accept policy puts no word on the wire at all — the field is absent, and the **Accept**
2177
2177
  press is Omni's doing, not the provider's.
@@ -2185,8 +2185,13 @@ belongs, whatever the host was configured with.
2185
2185
 
2186
2186
  An automatically accepted task still arrives through `task-offered`.
2187
2187
 
2188
- Agent-initiated work arrives through `task-offered` with the task's
2189
- `acceptance: "automatic"`.
2188
+ **Work the agent originated is accepted by the command that created it.** A task born of the
2189
+ agent's own act — a dialpad call or a connect-back, recognisable by the host's `dialId` on
2190
+ `onCall`; a lead's join, carrying `assisting`; a lead's monitor, carrying `monitoring` — arrives
2191
+ through `task-offered` with `acceptance: "automatic"` whatever `autoAcceptTasks` says, and the
2192
+ validator holds it there under either provisioning and under none (`task.acceptance.originated`):
2193
+ the desk shows no **Accept** for a call the agent placed. The provisioning governs work the queue
2194
+ routes to the agent, and nothing else.
2190
2195
 
2191
2196
  ### Pending
2192
2197
 
@@ -2220,14 +2225,20 @@ on a desk phone — and is not in question per call. See **How the agent hears t
2220
2225
  Automatic acceptance still begins with `task-offered`.
2221
2226
 
2222
2227
  `allocationExpiresAt` is the deadline after which the offer lapses. Where present, Omni counts
2223
- down and stops offering **Accept** once it passes. **Omit it unless the provider can observe it.**
2228
+ down and stops offering **Accept** once it passes; the provider ends the lapsed offer with
2229
+ `task-ended` and an `expired` outcome naming `pending`, since nobody cancelled it. **Omit it unless the provider can observe it.**
2224
2230
  A provider that reports only elapsed ring time after the fact cannot say when an offer is due
2225
2231
  to end, and a computed value would have Omni withdraw **Accept** from a task still pending.
2226
2232
 
2227
2233
  A preview's deadline is not on the offer. It travels on the task, as `previewEndsAt` with
2228
2234
  `atDeadline`, so a snapshot carries it too -- see **Preview: the agent presses Call**.
2229
2235
 
2230
- A provider may withdraw a pending task by emitting `task-ended` with a `cancelled` outcome.
2236
+ A provider may withdraw a pending task by emitting `task-ended` with a `cancelled` outcome, `by:
2237
+ "provider"`. **`cancelled` says who called the work off**, as `completed` says who completed it:
2238
+ `agent` for a decline, `provider` for a withdrawal or a re-route, `party` for a caller who abandoned
2239
+ the ring. One word for the three left a supervisor's record unable to tell them apart, so `by` is
2240
+ required and closed (`event.taskEnded.outcome.cancelled.by`). An offer nobody acted on before
2241
+ `allocationExpiresAt` is not cancelled by anyone: it lapses, and ends `expired` naming `pending`.
2231
2242
 
2232
2243
  ### Tasks already in progress
2233
2244
 
@@ -2309,7 +2320,8 @@ The canonical task transitions are:
2309
2320
  | `preview` | Agent presses Call (`call`) and the customer answers, or the deadline `calls` and they answer | `in-progress` |
2310
2321
  | `preview` | The call goes out and nobody answers | `completing` |
2311
2322
  | `preview` | The deadline `expires` | Removed by `task-ended` with `expired` outcome |
2312
- | `pending` | Provider withdraws the allocation | Removed by `task-ended` with `cancelled` outcome |
2323
+ | `pending` | Provider withdraws the allocation, the agent declines, or the party abandons the ring | Removed by `task-ended` with `cancelled` outcome, `by` saying which |
2324
+ | `pending` | The offer lapses at `allocationExpiresAt` | Removed by `task-ended` with `expired` outcome naming `pending` |
2313
2325
  | No task | Snapshot reports work already underway | `in-progress` |
2314
2326
  | `in-progress` | Provider or agent pauses the task | `paused` |
2315
2327
  | `in-progress` | Agent starts a warm transfer (`transfer` `warm`); the customer is parked | `paused` |
@@ -2420,9 +2432,18 @@ whether the channel carries real-time media:
2420
2432
  | Channel | Wrap allowance starts at |
2421
2433
  | --- | --- |
2422
2434
  | Voice and any channel with real-time media | The `task-media-ended` event |
2423
- | Chat | When the conversation ends and the task enters `completing` |
2424
- | Email | After the message is sent and the task enters `completing` |
2425
- | Other non-media channels | The moment the task enters `completing` |
2435
+ | Chat | The `task-updated` that moves the task to `completing`: the provider's word that the conversation is closed on its side, the allowance running from that publication's `occurredAt` |
2436
+ | Email | The `task-updated` that moves the task to `completing`: the provider's word that the platform has accepted the outgoing message, the allowance running from that publication's `occurredAt` |
2437
+ | Other non-media channels | The `task-updated` that moves the task to `completing` |
2438
+
2439
+ **Off voice, `completing` is the provider's word that handling ended.** There is no media event to
2440
+ carry it, so the phase does; a host starts the wrap clock at that publication and nowhere else. It
2441
+ is optional: a conversation with nothing to wrap moves from `in-progress` to `task-ended` and
2442
+ `completing` is never published. Under `provider-automatic` with a non-zero `wrapAllowance` it is
2443
+ required, because the clock has to start somewhere: a chat or email task the provider completes
2444
+ from `in-progress` with an allowance to run gave the agent none of it, and the stream refuses the
2445
+ ending (`stream.taskEnded.unwrapped`). Under `agent-command` the agent's `complete` is the end,
2446
+ from `in-progress` or from `completing` alike.
2426
2447
 
2427
2448
  ```ts
2428
2449
  const emailCompletion = {
@@ -2867,7 +2888,10 @@ last statement stands and the failure is a `diagnostic`, and a task that was pub
2867
2888
  `queue` or `ungoverned` never returns to `undetermined`, on an update (`stream.taskUpdated.capabilitySource`)
2868
2889
  or on a resync snapshot (`stream.snapshot.capabilitySource`); a record once read never loses an
2869
2890
  entry the same two ways (`stream.taskUpdated.handlingHistory`, `stream.snapshot.handlingHistory`);
2870
- and a task does not go backwards (`stream.taskUpdated.phase`): the stream sees publications, not
2891
+ a snapshot carrying a task still at work does not forget the audio the stream held up, since media
2892
+ ends on `task-media-ended` and the call moves on (`stream.snapshot.media`); and a task does not go
2893
+ backwards, on an update or on a resync (`stream.taskUpdated.phase`, `stream.snapshot.phase`): the
2894
+ stream sees publications, not
2871
2895
  transitions, and a task may pass through a phase between two, so `pending` to `in-progress` stands
2872
2896
  with `confirmed` between them, and what is refused is a phase unreachable from the last one read by
2873
2897
  the transition table -- back to `pending`, back to `confirmed` or `preview` once work began, out of
@@ -4268,8 +4292,8 @@ beside the mapped one is what the full check costs a host; an adapter has it for
4268
4292
  validator holds a command only to a task that stands: a task handed in that is not one the wire
4269
4293
  published is named (`command.task`) rather than checked against.
4270
4294
 
4271
- Declining or rejecting a pending offer ends it without accepting or completing it. The provider
4272
- confirms the end with `task-ended` and a `cancelled` outcome.
4295
+ Declining a pending offer ends it without accepting or completing it. The provider confirms the
4296
+ end with `task-ended` and a `cancelled` outcome, `by: "agent"`.
4273
4297
 
4274
4298
  ### `execute(request)`
4275
4299
 
@@ -4760,7 +4784,11 @@ built and connected for the same login with the same context and the same `store
4760
4784
  must carry the task with that leg and the host's word, and the run goes on with the second as its
4761
4785
  connection to the end. A platform that holds the record hands it back, and an adapter that composed
4762
4786
  the record in memory has nothing and is named (`drive.reload.snapshot`, `drive.reload.history`,
4763
- `.rejected`). The second adapter is held to what the first was: the same provider
4787
+ `.rejected`). The second adapter's snapshot is taken as any resync is: held to what the stream knew
4788
+ before it replaces it, so a phase gone backwards, a record that shrank or audio forgotten on a task
4789
+ still at work is named by the stream's own rules (`stream.snapshot.phase`, `.handlingHistory`,
4790
+ `.media`) and a reload is a place those rules keep working, not one where they stop. The second
4791
+ adapter is held to what the first was: the same provider
4764
4792
  (`drive.reload.manifest`), a snapshot that stands as any snapshot must, and a record that lost none
4765
4793
  of the entries the first had published -- a record once read is not unread across a reload either.
4766
4794
  A reconnect on the same adapter object would prove nothing, since an in-process adapter's memory
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",