@xema/omni-protocol 0.1.25 → 0.1.27

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/testing.js CHANGED
@@ -1,4 +1,4 @@
1
- import { browserSessionKey, effectiveTiers, sameCapabilities, } from "./index.js";
1
+ import { browserSessionKey, effectiveLevels, sameCapabilities, } from "./index.js";
2
2
  import { assertNoViolations, validateAuthenticationState, validateEventEnvelope, validateHostReport, validateManifest, validateResult, validateSnapshot, } from "./validation.js";
3
3
  export { ProtocolConformanceError, assertNoViolations } from "./validation.js";
4
4
  /**
@@ -31,8 +31,8 @@ const STATE_SUBJECTS = [
31
31
  // Pinned to the event union the way validation pins its closed sets: a type added to
32
32
  // `ProviderEvent` without a row here, or a row it lacks, is a compile error.
33
33
  const EVENT_TYPES = {
34
- snapshot: true, "provider-status": true, "break-state": true, "task-offered": true, "task-updated": true,
35
- "task-media-ready": true, "task-media-ended": true, "task-ended": true, announcement: true, "provider-summary": true,
34
+ snapshot: true, "transport-status": true, "break-state": true, "task-offered": true, "task-updated": true,
35
+ "task-media-started": true, "task-media-ended": true, "task-ended": true, announcement: true, "queue-summary": true,
36
36
  "team-updated": true, "contacts-updated": true, "calendar-updated": true,
37
37
  };
38
38
  const CONTRACT_SUBJECTS = [
@@ -172,8 +172,8 @@ export async function exerciseAdapter(adapter, context, options = {}) {
172
172
  let disconnectWasClean = false;
173
173
  try {
174
174
  authenticationState = await authentication.state();
175
- const tiers = effectiveTiers(adapter.manifest.orgTiers).map(tier => tier.id);
176
- violations.push(...validateAuthenticationState(authenticationState, "authentication", { tiers }));
175
+ const levels = effectiveLevels(adapter.manifest.orgLevels).map(level => level.id);
176
+ violations.push(...validateAuthenticationState(authenticationState, "authentication", { levels }));
177
177
  if (authenticationState.status !== "authenticated") {
178
178
  throw new Error(`Adapter contract exercise requires authenticated test state, received ${authenticationState.status}`);
179
179
  }
@@ -193,7 +193,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
193
193
  const reader = () => ({
194
194
  self: current().identity.id,
195
195
  capabilities: current().capabilities,
196
- sessionId: context.sessionId,
196
+ loginId: context.loginId,
197
197
  autoAcceptTasks: context.autoAcceptTasks ?? true,
198
198
  });
199
199
  // The optional methods are optional only until something declares a need for them. Each
@@ -228,7 +228,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
228
228
  requireMethod(on, "setPreference", "the login declares capabilities.preferences");
229
229
  };
230
230
  unsubscribeAuthentication = authentication.subscribe(state => {
231
- const own = validateAuthenticationState(state, "authentication", { tiers });
231
+ const own = validateAuthenticationState(state, "authentication", { levels });
232
232
  violations.push(...own);
233
233
  if (own.length > 0)
234
234
  return;
@@ -590,7 +590,7 @@ const WORK_BEGUN = new Set(["in-progress", "paused", "completing"]);
590
590
  export class TaskStream {
591
591
  tasks = new Map();
592
592
  static stated(task) {
593
- const media = isRecord(task) && (task.media === "ready" || task.media === "ended") ? task.media : "none";
593
+ const media = isRecord(task) && (task.media === "started" || task.media === "ended") ? task.media : "none";
594
594
  return { phase: String(isRecord(task) ? task.phase : undefined), media };
595
595
  }
596
596
  /** Replaces what is known with a snapshot's tasks, as a snapshot replaces Omni's state. */
@@ -638,30 +638,30 @@ export class TaskStream {
638
638
  }
639
639
  }
640
640
  // A task replaces the task, and an update re-states media without moving it: the
641
- // transitions belong to task-media-ready and task-media-ended. Releasing ended is the
641
+ // transitions belong to task-media-started and task-media-ended. Releasing ended is the
642
642
  // one move an update may make, since wrapped audio has nothing left to end.
643
643
  {
644
644
  const next = TaskStream.stated(event.task);
645
645
  if (known.media !== next.media && !(known.media === "ended" && next.media === "none")) {
646
- refuse("stream.taskUpdated.media", `${at}.task.media`, `a task-updated re-states media, it does not move it: ${id} held ${known.media} and the update says ${next.media}; audio arrives on task-media-ready and ends on task-media-ended`);
646
+ refuse("stream.taskUpdated.media", `${at}.task.media`, `a task-updated re-states media, it does not move it: ${id} held ${known.media} and the update says ${next.media}; audio arrives on task-media-started and ends on task-media-ended`);
647
647
  }
648
648
  this.tasks.set(id, next);
649
649
  }
650
650
  break;
651
- case "task-media-ready":
651
+ case "task-media-started":
652
652
  if (id === undefined)
653
653
  break;
654
654
  if (known === undefined) {
655
- refuse("stream.taskMediaReady.unknown", `${at}.taskId`, `${id} was never offered or carried on a snapshot`);
655
+ refuse("stream.taskMediaStarted.unknown", `${at}.taskId`, `${id} was never offered or carried on a snapshot`);
656
656
  break;
657
657
  }
658
658
  if (!WORK_BEGUN.has(known.phase)) {
659
- refuse("stream.taskMediaReady.beforeWork", `${at}.taskId`, `media cannot arrive on ${id} while it is ${known.phase}: a task is never its audio, and its work has not begun`);
659
+ refuse("stream.taskMediaStarted.beforeWork", `${at}.taskId`, `media cannot arrive on ${id} while it is ${known.phase}: a task is never its audio, and its work has not begun`);
660
660
  }
661
- if (known.media === "ready") {
662
- refuse("stream.taskMediaReady.duplicate", `${at}.taskId`, `media is already ready on ${id}; ready and ended alternate`);
661
+ if (known.media === "started") {
662
+ refuse("stream.taskMediaStarted.duplicate", `${at}.taskId`, `media already started on ${id}; started and ended alternate`);
663
663
  }
664
- known.media = "ready";
664
+ known.media = "started";
665
665
  break;
666
666
  case "task-media-ended":
667
667
  if (id === undefined)
@@ -673,8 +673,8 @@ export class TaskStream {
673
673
  if (!WORK_BEGUN.has(known.phase)) {
674
674
  refuse("stream.taskMediaEnded.beforeWork", `${at}.taskId`, `media cannot end on ${id} while it is ${known.phase}: a task is never its audio, and its work has not begun`);
675
675
  }
676
- if (known.media !== "ready") {
677
- refuse("stream.taskMediaEnded.silent", `${at}.taskId`, `media cannot end on ${id} where none arrived: audio attaches on task-media-ready, or on a task carried with media ready`);
676
+ if (known.media !== "started") {
677
+ refuse("stream.taskMediaEnded.silent", `${at}.taskId`, `media cannot end on ${id} where none arrived: audio attaches on task-media-started, or on a task carried with media started`);
678
678
  }
679
679
  known.media = "ended";
680
680
  break;
@@ -756,7 +756,7 @@ export function assertBreakFollowsItsRequests(envelopes, snapshot) {
756
756
  }
757
757
  /**
758
758
  * The media follows the task and never decides it. Given a provider's stream -- optionally seeded
759
- * with the snapshot it began from -- every task is introduced once, `task-media-ready` and
759
+ * with the snapshot it began from -- every task is introduced once, `task-media-started` and
760
760
  * `task-media-ended` alternate on work that has begun, media ends only where it arrived, and what
761
761
  * follows the media ending is `completing` or `task-ended`.
762
762
  */
@@ -769,7 +769,7 @@ export function assertMediaFollowsTheTask(envelopes, snapshot) {
769
769
  assertNoViolations(found, "The media follows the task");
770
770
  }
771
771
  /** A host that reports one thing and never changes: what most adapter tests hand `exerciseAdapter`. */
772
- export function stillHost(report = { online: true, browsers: { urlVisibility: "hidden" } }) {
772
+ export function stillHost(report = { online: true }) {
773
773
  return { report: () => report, subscribe: () => () => undefined };
774
774
  }
775
775
  const usableLogin = (status) => status === "authenticated" || status === "refreshing";
@@ -833,20 +833,20 @@ export function assertBreakBeginsAfterTask(steps) {
833
833
  * implementation; pass 0 to demand an exact match.
834
834
  */
835
835
  export function assertWrapTimeout(task, mediaEndedAt, observedDeadline, toleranceMs = 1_000) {
836
- if (task.completionAllowance === undefined) {
836
+ if (task.wrapAllowance === undefined) {
837
837
  if (observedDeadline !== undefined) {
838
838
  throw new Error(`Wrap deadline mismatch: the task states no allowance, so there is no deadline, received ${observedDeadline}`);
839
839
  }
840
840
  return;
841
841
  }
842
842
  if (observedDeadline === undefined) {
843
- throw new Error(`Wrap deadline mismatch: the task allows ${task.completionAllowance}s, but no deadline was observed`);
843
+ throw new Error(`Wrap deadline mismatch: the task allows ${task.wrapAllowance}s, but no deadline was observed`);
844
844
  }
845
845
  const ended = Date.parse(mediaEndedAt);
846
846
  const deadline = Date.parse(observedDeadline);
847
847
  if (Number.isNaN(ended) || Number.isNaN(deadline))
848
848
  throw new Error("Wrap scenario requires valid ISO-8601 times");
849
- const expected = ended + task.completionAllowance * 1_000;
849
+ const expected = ended + task.wrapAllowance * 1_000;
850
850
  if (Math.abs(deadline - expected) > toleranceMs) {
851
851
  throw new Error(`Wrap deadline mismatch: expected ${new Date(expected).toISOString()} within ${toleranceMs}ms, received ${observedDeadline}`);
852
852
  }
@@ -854,14 +854,14 @@ export function assertWrapTimeout(task, mediaEndedAt, observedDeadline, toleranc
854
854
  /** The session key one scenario derives, or `undefined` where the browser shares nothing. */
855
855
  const sessionKeyFor = (scenario) => browserSessionKey(scenario);
856
856
  /** Validates whether two task-browser definitions should share one browser session. */
857
- export function assertBrowserIsolationAndReuse(left, right, expectedReuse) {
857
+ export function assertBrowserSessionIsolation(left, right, expectedReuse) {
858
858
  const leftKey = sessionKeyFor(left);
859
859
  const rightKey = sessionKeyFor(right);
860
- // A browser that does not reuse has no session key at all, so two of them never share one.
861
- // Treating "no key" as a match would report reuse nobody asked for.
860
+ // A browser that does not share its session has no session key at all, so two of them never share one.
861
+ // Treating "no key" as a match would report sharing nobody asked for.
862
862
  const actualReuse = leftKey !== undefined && leftKey === rightKey;
863
863
  if (actualReuse !== expectedReuse) {
864
- throw new Error(`Browser reuse mismatch: expected ${expectedReuse}, received ${actualReuse} (${String(leftKey)} vs ${String(rightKey)})`);
864
+ throw new Error(`Browser session sharing mismatch: expected ${expectedReuse}, received ${actualReuse} (${String(leftKey)} vs ${String(rightKey)})`);
865
865
  }
866
866
  }
867
867
  /**
@@ -1,4 +1,4 @@
1
- import { type ProtocolViolation, type SessionCapabilities, type UserId } from "./index.js";
1
+ import { type ProtocolViolation, type UserCapabilities, type UserId } from "./index.js";
2
2
  export type { ProtocolViolation } from "./index.js";
3
3
  export declare class ProtocolConformanceError extends Error {
4
4
  readonly violations: readonly ProtocolViolation[];
@@ -13,8 +13,8 @@ export declare function validateManifest(manifest: unknown, path?: string): Prot
13
13
  export interface TaskValidationContext {
14
14
  /** The provider's channel, from its manifest. A task must agree with it. */
15
15
  channel: string;
16
- /** The tier ids in force, from the manifest. The defaults when absent. */
17
- tiers?: readonly string[];
16
+ /** The level ids in force, from the manifest. The defaults when absent. */
17
+ levels?: readonly string[];
18
18
  }
19
19
  export declare function validateTask(task: unknown, context: TaskValidationContext, path?: string): ProtocolViolation[];
20
20
  /**
@@ -31,11 +31,11 @@ export interface ReaderContext {
31
31
  * The login's `AuthenticationState.capabilities`. The login is the permission: a lead's
32
32
  * snapshot carries a roster, nobody else's does, and `requests` need `team.consultControl`.
33
33
  */
34
- capabilities?: SessionCapabilities;
35
- /** The tier ids in force. Filled from the manifest by `validateSnapshot` and `validateEventEnvelope`; the defaults otherwise. */
36
- tiers?: readonly string[];
37
- /** The login's `sessionId`. A snapshot or event naming another belongs to a login that is gone. */
38
- sessionId?: string;
34
+ capabilities?: UserCapabilities;
35
+ /** The level ids in force. Filled from the manifest by `validateSnapshot` and `validateEventEnvelope`; the defaults otherwise. */
36
+ levels?: readonly string[];
37
+ /** The login's `loginId`. A snapshot or event naming another belongs to a login that is gone. */
38
+ loginId?: string;
39
39
  /** `ConnectContext.autoAcceptTasks` as sent, absent meaning `true`: whether `task-offered` carries an `acceptanceMode`. */
40
40
  autoAcceptTasks?: boolean;
41
41
  }
@@ -59,7 +59,7 @@ export type ResultMethod = "execute" | "dial" | "setCapacity" | "requestBreak" |
59
59
  export declare function validateResult(result: unknown, method: ResultMethod, path?: string): ProtocolViolation[];
60
60
  /** What a login is validated against beyond its own shape. */
61
61
  export interface LoginValidationContext {
62
- /** The tier ids in force, from the manifest. The defaults when absent. */
63
- tiers?: readonly string[];
62
+ /** The level ids in force, from the manifest. The defaults when absent. */
63
+ levels?: readonly string[];
64
64
  }
65
65
  export declare function validateAuthenticationState(state: unknown, path?: string, context?: LoginValidationContext): ProtocolViolation[];