@xema/omni-protocol 0.1.112 → 0.1.113

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,5 +1,5 @@
1
1
  import { browserSessionKey, effectiveLevels, sameCapabilities, } from "./index.js";
2
- import { assertNoViolations, validateAuthenticationState, validateBreakTransition, validateEventEnvelope, validateHostGuarantees, validateHostReport, validateUserDetails, observeRules, ruleEvaluated, validateHostMute, validateHostRecording, validateLoginStore, validateHistoryReport, validateManifest, validateResult, validateSnapshot, validateTask, validateTimeZone, validatePhone, validateTaskCommand, isTimeZone, sameTimeZone, } from "./validation.js";
2
+ import { assertNoViolations, validateAuthenticationState, validateBreakTransition, validateEventEnvelope, validateHostGuarantees, validateHostReport, validateUserDetails, observeRules, ruleTested, validateHostMute, validateHostRecording, validateLoginStore, validateHistoryReport, validateManifest, validateResult, validateSnapshot, validateTask, validateTimeZone, validatePhone, validateTaskCommand, isTimeZone, sameTimeZone, } from "./validation.js";
3
3
  export { ProtocolConformanceError, assertNoViolations } from "./validation.js";
4
4
  /**
5
5
  * A part of the contract a run may never reach: state nothing obliges an adapter to publish, so
@@ -212,9 +212,9 @@ function observeEvent(envelope, seen) {
212
212
  * for a synchronous emitter, and is swallowed as an unhandled rejection for an
213
213
  * asynchronous one — which would let a non-conforming async adapter pass.
214
214
  */
215
- export async function exerciseAdapter(adapter, context, options = {}) {
216
- const rulesEvaluated = new Set();
217
- const stopObserving = observeRules(rule => { rulesEvaluated.add(rule); });
215
+ export async function testAdapter(adapter, context, options = {}) {
216
+ const rulesTested = new Set();
217
+ const stopObserving = observeRules(rule => { rulesTested.add(rule); });
218
218
  const violations = [...validateManifest(adapter.manifest)];
219
219
  const events = [];
220
220
  const seen = new Set();
@@ -388,7 +388,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
388
388
  const tellRefused = (report) => {
389
389
  if (typeof live.refused !== "function")
390
390
  return;
391
- ruleEvaluated("connection.refused.rejected");
391
+ ruleTested("connection.refused.rejected");
392
392
  try {
393
393
  live.refused(report);
394
394
  }
@@ -398,7 +398,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
398
398
  };
399
399
  const eventPayloads = new Map();
400
400
  let capacityStated;
401
- // The drive waits on events: each waiter is offered every envelope as it lands.
401
+ // The test waits on events: each waiter is offered every envelope as it lands.
402
402
  const waiters = new Set();
403
403
  const onEnvelope = (envelope) => {
404
404
  observeEvent(envelope, seen);
@@ -408,10 +408,10 @@ export async function exerciseAdapter(adapter, context, options = {}) {
408
408
  tellRefused({ artefact: "event", envelopeId: typeof envelope?.id === "string" ? envelope.id : undefined, violations: broken });
409
409
  // A diagnostic is informational to a host and a failure to a conformance run: the platform
410
410
  // under test broke a rule the adapter relies on, and a green result must not paper over it.
411
- // A reload in life has no first connection: the page is gone. The drive keeps its first
411
+ // A reload in life has no first connection: the page is gone. The test keeps its first
412
412
  // connection up while the second reads, so a platform that pushes the open task to the new
413
413
  // client pushes it to the old one too, and an adapter that calls a re-offer of answered work a
414
- // defect is right to. That diagnostic is the drive's own artefact, and is not counted while the
414
+ // defect is right to. That diagnostic is the test's own artefact, and is not counted while the
415
415
  // second adapter is up; every other moment it is.
416
416
  if (isRecord(envelope?.event) && envelope.event.type === "diagnostic") {
417
417
  violations.push({ rule: "diagnostic.raised", path: "event.diagnostic",
@@ -469,7 +469,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
469
469
  waiter(envelope);
470
470
  };
471
471
  unsubscribe = connection.subscribe(onEnvelope);
472
- // A host reload is the first client dying and a second coming up in its place. The drive hands
472
+ // A host reload is the first client dying and a second coming up in its place. The test hands
473
473
  // the run over: the first connection is unsubscribed, disconnected and its session closed before
474
474
  // the second connects, and the run continues on the second -- so there is no first client for a
475
475
  // platform to push the open task back to, and nothing to exempt.
@@ -519,8 +519,8 @@ export async function exerciseAdapter(adapter, context, options = {}) {
519
519
  // holds its connect snapshot to no team until it is sent again, after its capacity.
520
520
  leadFeaturesOn = false;
521
521
  const restated = await second.setCapacity({ count: capacityStated ?? 1 });
522
- violations.push(...validateResult(restated, "setCapacity", "drive.reload.setCapacity"));
523
- await switchOnLeadFeatures(second, "drive.reload");
522
+ violations.push(...validateResult(restated, "setCapacity", "test.reload.setCapacity"));
523
+ await switchOnLeadFeatures(second, "test.reload");
524
524
  },
525
525
  };
526
526
  // Every snapshot the run reads, the connect snapshot and a reloaded client's, is read the same
@@ -578,7 +578,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
578
578
  continue;
579
579
  }
580
580
  // Evaluated where a superseded event was held: with none, the question was never asked.
581
- ruleEvaluated("snapshot.accounts.task", "snapshot.accounts.ended");
581
+ ruleTested("snapshot.accounts.task", "snapshot.accounts.ended");
582
582
  if ((event.type === "task-offered" || event.type === "task-updated") && isRecord(event.task) && typeof event.task.assignmentId === "string" && !carried.has(event.task.assignmentId)) {
583
583
  violations.push({ rule: "snapshot.accounts.task", path: "snapshot.tasks",
584
584
  message: `${String(event.task.assignmentId)} (${event.task.assignmentId}) was published while the snapshot was read and the snapshot does not carry it: a snapshot accounts for everything the adapter emitted before it resolved` });
@@ -596,7 +596,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
596
596
  // ready, and on every change. An adapter that never asked cannot have.
597
597
  // The obligation has two halves: read the report before declaring the agent ready, and hear of
598
598
  // every change after. An adapter that did one and not the other consulted the host once, or never.
599
- ruleEvaluated("connection.host.consulted", "connection.host.subscribed");
599
+ ruleTested("connection.host.consulted", "connection.host.subscribed");
600
600
  if (adapter.manifest.channel === "voice" && !consulted.report) {
601
601
  violations.push({ rule: "connection.host.consulted", path: "connection.host",
602
602
  message: "a voice adapter reads the host's report before it declares the agent ready to its platform, and this one never asked" });
@@ -642,8 +642,8 @@ export async function exerciseAdapter(adapter, context, options = {}) {
642
642
  if (switched === undefined)
643
643
  return;
644
644
  violations.push(...validateResult(switched, "executeTeam", `${at}.executeTeam`));
645
- ruleEvaluated("team.required");
646
- const ms = options.driveTimeoutMs ?? 5000;
645
+ ruleTested("team.required");
646
+ const ms = options.timeoutMs ?? 5000;
647
647
  const isBaseline = (envelope) => isRecord(envelope?.event) && envelope.event.type === "team-updated";
648
648
  const baseline = events.slice(from).some(isBaseline) || await new Promise(resolve => {
649
649
  const timer = setTimeout(() => { waiters.delete(waiter); resolve(false); }, ms);
@@ -661,19 +661,19 @@ export async function exerciseAdapter(adapter, context, options = {}) {
661
661
  message: `the application switched the team feature on and no team-updated followed within ${ms}ms: the whole team is owed once it is on, members: [] when nobody is in it` });
662
662
  };
663
663
  await switchOnLeadFeatures(live, "connection");
664
- if (options.drive) {
664
+ if (options.withCall) {
665
665
  const localAudio = isRecord(first) && isRecord(first.audio) && isRecord(first.audio.input) && first.audio.input.status === "available"
666
666
  ? first.audio.input.localAudio : undefined;
667
- violations.push(...await driveOneCall({
667
+ violations.push(...await testOneCall({
668
668
  connection: live, manifest: adapter.manifest, channel: adapter.manifest.channel, softphone, snapshot, events, waiters, stream, localAudio,
669
669
  everHeldForTask: (taskId) => [...watched.everHeld].some(key => namesTask(key, taskId)), snapshotRead,
670
- timeoutMs: options.driveTimeoutMs ?? 5000,
670
+ timeoutMs: options.timeoutMs ?? 5000,
671
671
  context: connected, secrets: authenticationSecrets, reader, rebuild: options.rebuild, held: watched.held, handOver,
672
672
  streams: { stream, breaks, team },
673
673
  }));
674
- // Every offer is owed an ending. The drive ends the call it drove; anything else the
674
+ // Every offer is owed an ending. The test ends the call it drove; anything else the
675
675
  // provider offered and left open is an assignment whose ending never came.
676
- ruleEvaluated("stream.taskOffered.unended");
676
+ ruleTested("stream.taskOffered.unended");
677
677
  for (const id of stream.unended()) {
678
678
  violations.push({ rule: "stream.taskOffered.unended", path: "event.task-ended",
679
679
  message: `${id} was offered and never ended: every offer is owed a task-ended, whatever became of the call` });
@@ -750,10 +750,10 @@ export async function exerciseAdapter(adapter, context, options = {}) {
750
750
  events: events,
751
751
  authenticationState: authenticationState,
752
752
  login: (login ?? authenticationState),
753
- notExercised: CONTRACT_SUBJECTS.filter(subject => !seen.has(subject)),
753
+ notTested: CONTRACT_SUBJECTS.filter(subject => !seen.has(subject)),
754
754
  disconnectWasClean,
755
755
  violations,
756
- rulesEvaluated,
756
+ rulesTested,
757
757
  };
758
758
  }
759
759
  /**
@@ -1036,7 +1036,7 @@ export function assertTaskCapabilityWithdrawal(tasks, manifest, command) {
1036
1036
  * so a fixture that never produced a team member list cannot pass a test that meant to check one.
1037
1037
  */
1038
1038
  export function assertReached(result, subjects) {
1039
- const missed = subjects.filter(subject => result.notExercised.includes(subject));
1039
+ const missed = subjects.filter(subject => result.notTested.includes(subject));
1040
1040
  if (missed.length > 0) {
1041
1041
  throw new Error(`The exercise never reached ${missed.join(", ")}: its clean result says nothing about them`);
1042
1042
  }
@@ -1159,7 +1159,7 @@ export class TaskStream {
1159
1159
  }
1160
1160
  const before = known.get(field);
1161
1161
  if (before !== undefined) {
1162
- ruleEvaluated("stream.countdown.copied");
1162
+ ruleTested("stream.countdown.copied");
1163
1163
  const elapsed = Math.floor((atMs - before.atMs) / 1000);
1164
1164
  if (elapsed > 0 && value > before.value - elapsed + 1) {
1165
1165
  refuse("stream.countdown.copied", `${at}.${field}`, `${id} said ${field} ${before.value} at ${new Date(before.atMs).toISOString()} and ${value} at ${new Date(atMs).toISOString()}: ${elapsed}s passed on the provider's clock and the countdown lost fewer; seconds left are worked out at each publication, never copied from the one before`);
@@ -1265,7 +1265,7 @@ export class TaskStream {
1265
1265
  resync(snapshot, at) {
1266
1266
  const found = [];
1267
1267
  const refuse = (rule, where, message) => found.push({ rule, path: where, message });
1268
- ruleEvaluated("stream.snapshot.capabilitySource", "stream.snapshot.history", "stream.snapshot.phase", "stream.snapshot.audio");
1268
+ ruleTested("stream.snapshot.capabilitySource", "stream.snapshot.history", "stream.snapshot.phase", "stream.snapshot.audio");
1269
1269
  if (isRecord(snapshot) && Array.isArray(snapshot.tasks)) {
1270
1270
  snapshot.tasks.forEach((task, index) => {
1271
1271
  if (!isRecord(task) || typeof task.assignmentId !== "string")
@@ -1331,7 +1331,7 @@ export class TaskStream {
1331
1331
  found.push(...this.resync(event.snapshot, `${at}.snapshot`));
1332
1332
  break;
1333
1333
  case "task-offered": {
1334
- ruleEvaluated("stream.taskOffered.duplicate");
1334
+ ruleTested("stream.taskOffered.duplicate");
1335
1335
  if (id === undefined)
1336
1336
  break;
1337
1337
  // An assignment is introduced once: not while it is open, and never again once it has ended,
@@ -1349,14 +1349,14 @@ export class TaskStream {
1349
1349
  case "task-updated":
1350
1350
  if (id === undefined)
1351
1351
  break;
1352
- ruleEvaluated("stream.taskUpdated.unknown");
1352
+ ruleTested("stream.taskUpdated.unknown");
1353
1353
  if (known === undefined) {
1354
1354
  refuse("stream.taskUpdated.unknown", `${at}.task.assignmentId`, `${id} was never offered or carried on a snapshot`);
1355
1355
  break;
1356
1356
  }
1357
1357
  // The rules about a known task are evaluated only once there is one.
1358
1358
  this.holdCountdowns(id, event.task, isRecord(envelope) ? envelope.occurredAt : undefined, `${at}.task`, refuse);
1359
- ruleEvaluated("stream.taskUpdated.capabilitySource", "stream.taskUpdated.phase", "stream.taskUpdated.audioOpen", "stream.taskUpdated.history", "stream.taskAudioEnded.follow", "stream.taskUpdated.audio", "stream.taskUpdated.stage", "stream.taskUpdated.stage.lingering");
1359
+ ruleTested("stream.taskUpdated.capabilitySource", "stream.taskUpdated.phase", "stream.taskUpdated.audioOpen", "stream.taskUpdated.history", "stream.taskAudioEnded.follow", "stream.taskUpdated.audio", "stream.taskUpdated.stage", "stream.taskUpdated.stage.lingering");
1360
1360
  // Terms once read stay read. A re-read that fails is not a new fact about the task, so the
1361
1361
  // last statement stands and the failure is a diagnostic; not-yet-read is a place a task
1362
1362
  // starts from, never one it returns to.
@@ -1388,7 +1388,7 @@ export class TaskStream {
1388
1388
  }
1389
1389
  // A task completes after its audio ends, never around it: an update that moves a task to
1390
1390
  // completing while the stream holds its audio as started is a call whose audio never ended,
1391
- // whoever caused the ending -- the drive's end-call, a take-over, the provider's own hand.
1391
+ // whoever caused the ending -- the test's end-call, a take-over, the provider's own hand.
1392
1392
  {
1393
1393
  const to = isRecord(event.task) ? String(event.task.phase) : "";
1394
1394
  if (to === "completing" && known.audio === "started" && known.phase !== "completing") {
@@ -1428,7 +1428,7 @@ export class TaskStream {
1428
1428
  this.noteDials(event.task);
1429
1429
  break;
1430
1430
  case "task-audio-started":
1431
- ruleEvaluated("stream.taskAudioStarted.beforeWork", "stream.taskAudioStarted.duplicate", "stream.taskAudio.channel", "stream.assignment.ended", "stream.assignment.unknown");
1431
+ ruleTested("stream.taskAudioStarted.beforeWork", "stream.taskAudioStarted.duplicate", "stream.taskAudio.channel", "stream.assignment.ended", "stream.assignment.unknown");
1432
1432
  if (id === undefined)
1433
1433
  break;
1434
1434
  if (known === undefined) {
@@ -1450,7 +1450,7 @@ export class TaskStream {
1450
1450
  known.audio = "started";
1451
1451
  break;
1452
1452
  case "task-audio-ended":
1453
- ruleEvaluated("stream.taskAudioEnded.beforeWork", "stream.taskAudioEnded.silent", "stream.taskAudio.channel", "stream.assignment.ended", "stream.assignment.unknown");
1453
+ ruleTested("stream.taskAudioEnded.beforeWork", "stream.taskAudioEnded.silent", "stream.taskAudio.channel", "stream.assignment.ended", "stream.assignment.unknown");
1454
1454
  if (id === undefined)
1455
1455
  break;
1456
1456
  if (known === undefined) {
@@ -1470,7 +1470,7 @@ export class TaskStream {
1470
1470
  known.audio = "ended";
1471
1471
  break;
1472
1472
  case "task-ended":
1473
- ruleEvaluated("stream.assignment.ended", "stream.assignment.unknown");
1473
+ ruleTested("stream.assignment.ended", "stream.assignment.unknown");
1474
1474
  if (id === undefined)
1475
1475
  break;
1476
1476
  if (known === undefined) {
@@ -1480,7 +1480,7 @@ export class TaskStream {
1480
1480
  // A voice task ends after its audio ends, never around it, whatever the outcome: a take-over
1481
1481
  // and a lead leaving included. An ending with the audio still up leaves the host holding an
1482
1482
  // open audio session and an open leg on a task that no longer exists.
1483
- ruleEvaluated("stream.taskEnded.audioOpen");
1483
+ ruleTested("stream.taskEnded.audioOpen");
1484
1484
  if (known.audio === "started") {
1485
1485
  refuse("stream.taskEnded.audioOpen", `${at}.assignmentId`, `${id} ended with its audio still started: the audio ends first, on task-audio-ended, whatever the outcome`);
1486
1486
  }
@@ -1488,7 +1488,7 @@ export class TaskStream {
1488
1488
  // provider's word that interaction ended and the moment the wrap allowance starts. A provider
1489
1489
  // that completes the task itself with an allowance to run has to have started the clock:
1490
1490
  // completed from in-progress, the allowance it stated was never given.
1491
- ruleEvaluated("stream.taskEnded.unwrapped");
1491
+ ruleTested("stream.taskEnded.unwrapped");
1492
1492
  // An agent's own complete cuts the wrap short from wherever the task stands, so that ending says by agent and is not this.
1493
1493
  if (known.channel !== "voice" && known.completionMode === "provider-automatic" && known.wrapAllowance !== undefined && known.wrapAllowance > 0
1494
1494
  && known.phase !== "completing" && isRecord(event.outcome) && event.outcome.type === "completed" && event.outcome.by === "provider") {
@@ -1501,7 +1501,7 @@ export class TaskStream {
1501
1501
  // An outcome ends a dial somebody placed, once. The task it names may already have ended;
1502
1502
  // a dial placed late routinely outlives its call, which is why the dial has its own identity,
1503
1503
  // and why the outcome names the assignment: the host routes it to that life, ended or not.
1504
- ruleEvaluated("stream.assignment.unknown");
1504
+ ruleTested("stream.assignment.unknown");
1505
1505
  if (typeof event.assignmentId === "string" && !this.assignments.has(event.assignmentId)) {
1506
1506
  refuse("stream.assignment.unknown", `${at}.assignmentId`, `${event.assignmentId} is not an assignment this stream has seen`);
1507
1507
  }
@@ -1527,24 +1527,24 @@ export class TaskStream {
1527
1527
  * One ordinary lifecycle on the first task offered, each step held to the rules a host holds a
1528
1528
  * provider to: the command validated against the task as published, the result validated for the
1529
1529
  * method, and the event the provider owes in return awaited -- a step that never arrives is a
1530
- * violation naming what was owed. The drive stops where the task offers no way on (no `endCall`,
1530
+ * violation naming what was owed. The test stops where the task offers no way on (no `endCall`,
1531
1531
  * a provider that completes for itself) and says nothing about what it could not reach.
1532
1532
  */
1533
- async function driveOneCall(drive) {
1533
+ async function testOneCall(call) {
1534
1534
  const found = [];
1535
1535
  const refuse = (rule, path, message) => found.push({ rule, path, message });
1536
1536
  const isTask = (value) => isRecord(value) && typeof value.assignmentId === "string";
1537
1537
  // Wait for an envelope that satisfies `matches`, looking first at what already arrived.
1538
- const waitFor = (what, matches, from, within = { ms: drive.timeoutMs, onExpiry: () => refuse("drive.timeout", "drive", `the provider owed ${what} within ${drive.timeoutMs}ms and it never arrived`) }) => new Promise(resolve => {
1539
- for (let index = from; index < drive.events.length; index += 1) {
1540
- const hit = matches(drive.events[index]);
1538
+ const waitFor = (what, matches, from, within = { ms: call.timeoutMs, onExpiry: () => refuse("test.timeout", "test", `the provider owed ${what} within ${call.timeoutMs}ms and it never arrived`) }) => new Promise(resolve => {
1539
+ for (let index = from; index < call.events.length; index += 1) {
1540
+ const hit = matches(call.events[index]);
1541
1541
  if (hit !== undefined) {
1542
1542
  resolve({ found: hit, at: index + 1 });
1543
1543
  return;
1544
1544
  }
1545
1545
  }
1546
1546
  const timer = setTimeout(() => {
1547
- drive.waiters.delete(waiter);
1547
+ call.waiters.delete(waiter);
1548
1548
  within.onExpiry();
1549
1549
  resolve(undefined);
1550
1550
  }, within.ms);
@@ -1553,45 +1553,45 @@ async function driveOneCall(drive) {
1553
1553
  if (hit === undefined)
1554
1554
  return;
1555
1555
  clearTimeout(timer);
1556
- drive.waiters.delete(waiter);
1557
- resolve({ found: hit, at: drive.events.length });
1556
+ call.waiters.delete(waiter);
1557
+ resolve({ found: hit, at: call.events.length });
1558
1558
  };
1559
- drive.waiters.add(waiter);
1559
+ call.waiters.add(waiter);
1560
1560
  });
1561
1561
  // The call's audio session, opened on the first client and opened again on a reloaded second.
1562
1562
  let session;
1563
1563
  const recordSurvivesReload = async (at) => {
1564
1564
  // Each reload rule is marked where it is decided, never up front: a rebuild that throws has looked at nothing else.
1565
- ruleEvaluated("drive.reload.rejected");
1565
+ ruleTested("test.reload.rejected");
1566
1566
  let again;
1567
1567
  try {
1568
- again = drive.rebuild();
1568
+ again = call.rebuild();
1569
1569
  }
1570
1570
  catch (error) {
1571
- refuse("drive.reload.rejected", "drive.reload", `building the adapter again threw: ${String(error)}`);
1571
+ refuse("test.reload.rejected", "test.reload", `building the adapter again threw: ${String(error)}`);
1572
1572
  return;
1573
1573
  }
1574
1574
  // The same provider, built again: a different manifest is a different adapter, and proves nothing about this one.
1575
- ruleEvaluated("drive.reload.manifest");
1576
- if (again.manifest.id !== drive.manifest.id) {
1577
- refuse("drive.reload.manifest", "drive.reload.manifest", `rebuild returned an adapter for ${String(again.manifest.id)}; the reload is of ${String(drive.manifest.id)}`);
1575
+ ruleTested("test.reload.manifest");
1576
+ if (again.manifest.id !== call.manifest.id) {
1577
+ refuse("test.reload.manifest", "test.reload.manifest", `rebuild returned an adapter for ${String(again.manifest.id)}; the reload is of ${String(call.manifest.id)}`);
1578
1578
  return;
1579
1579
  }
1580
1580
  // The first client dies first, as it does on a reload: from here the run has no connection until the second stands.
1581
- ruleEvaluated("drive.reload.handover");
1582
- if (!await drive.handOver.down()) {
1583
- refuse("drive.reload.handover", "drive.reload", "taking the first client down threw: an unsubscribe, disconnect() or close() failed");
1581
+ ruleTested("test.reload.handover");
1582
+ if (!await call.handOver.down()) {
1583
+ refuse("test.reload.handover", "test.reload", "taking the first client down threw: an unsubscribe, disconnect() or close() failed");
1584
1584
  }
1585
1585
  try {
1586
- const restoredSession = await again.createAuthenticationSession({ ...drive.context, secrets: drive.secrets });
1586
+ const restoredSession = await again.createAuthenticationSession({ ...call.context, secrets: call.secrets });
1587
1587
  // The reload is a restore before it is anything else: the second session stands authenticated
1588
1588
  // as the same person, from the secrets alone, or nothing it reads afterwards is this login's.
1589
1589
  const restored = await restoredSession.state();
1590
- found.push(...validateAuthenticationState(restored, "drive.reload.login"));
1591
- ruleEvaluated("drive.reload.login");
1592
- const same = isRecord(restored) && restored.status === "authenticated" && isRecord(restored.identity) && restored.identity.id === drive.reader().self;
1590
+ found.push(...validateAuthenticationState(restored, "test.reload.login"));
1591
+ ruleTested("test.reload.login");
1592
+ const same = isRecord(restored) && restored.status === "authenticated" && isRecord(restored.identity) && restored.identity.id === call.reader().self;
1593
1593
  if (!same) {
1594
- refuse("drive.reload.login", "drive.reload.login", `a second adapter built from the same login and secrets did not restore it: the session says ${String(isRecord(restored) ? restored.status : restored)}${isRecord(restored) && isRecord(restored.identity) ? ` as ${String(restored.identity.id)}` : ""}, and the login is ${String(drive.reader().self)}`);
1594
+ refuse("test.reload.login", "test.reload.login", `a second adapter built from the same login and secrets did not restore it: the session says ${String(isRecord(restored) ? restored.status : restored)}${isRecord(restored) && isRecord(restored.identity) ? ` as ${String(restored.identity.id)}` : ""}, and the login is ${String(call.reader().self)}`);
1595
1595
  // A reload that cannot restore the login is a sign-in screen, not a connection: the run ends here.
1596
1596
  try {
1597
1597
  await restoredSession.close();
@@ -1599,27 +1599,27 @@ async function driveOneCall(drive) {
1599
1599
  catch { /* the session that would not restore is closed as far as it can be */ }
1600
1600
  return;
1601
1601
  }
1602
- const second = await again.connect(drive.context);
1602
+ const second = await again.connect(call.context);
1603
1603
  // From here the second client is the run's connection, held to everything the first was on
1604
1604
  // connect: what it publishes is validated as before.
1605
- await drive.handOver.up(restoredSession, second);
1606
- drive.connection = second;
1605
+ await call.handOver.up(restoredSession, second);
1606
+ call.connection = second;
1607
1607
  const snapshot = await second.snapshot();
1608
1608
  // The second adapter's snapshot is read as the connect snapshot was, and the streams take it
1609
1609
  // as the state now the way they take any resync: held to what they knew, then replaced. A reload
1610
1610
  // is a place the stream's rules keep working, not one where they all stop.
1611
- await drive.snapshotRead(second, snapshot, "drive.reload.snapshot");
1612
- found.push(...drive.streams.stream.resync(snapshot, "drive.reload.snapshot"));
1613
- found.push(...drive.streams.breaks.seed(snapshot));
1614
- drive.streams.team.seed(snapshot);
1611
+ await call.snapshotRead(second, snapshot, "test.reload.snapshot");
1612
+ found.push(...call.streams.stream.resync(snapshot, "test.reload.snapshot"));
1613
+ found.push(...call.streams.breaks.seed(snapshot));
1614
+ call.streams.team.seed(snapshot);
1615
1615
  const carried = isRecord(snapshot) && Array.isArray(snapshot.tasks)
1616
1616
  ? snapshot.tasks.find(task => isRecord(task) && task.assignmentId === taskId) : undefined;
1617
- ruleEvaluated("drive.reload.snapshot");
1617
+ ruleTested("test.reload.snapshot");
1618
1618
  if (carried === undefined) {
1619
- refuse("drive.reload.snapshot", "drive.reload.snapshot", `a second adapter built from the same login does not carry ${taskId} on its snapshot, and the task is still open`);
1619
+ refuse("test.reload.snapshot", "test.reload.snapshot", `a second adapter built from the same login does not carry ${taskId} on its snapshot, and the task is still open`);
1620
1620
  return;
1621
1621
  }
1622
- ruleEvaluated("drive.reload.history");
1622
+ ruleTested("test.reload.history");
1623
1623
  const history = carried.history;
1624
1624
  const steps = isRecord(history) && Array.isArray(history.steps) ? history.steps : [];
1625
1625
  // A record once read is not unread across a reload either: every entry the first adapter published is here.
@@ -1628,41 +1628,41 @@ async function driveOneCall(drive) {
1628
1628
  const lost = wasRead.filter(entry => !steps.some(now => isRecord(now) && now.step === entry.step && now.at === entry.at))
1629
1629
  .map(entry => `${String(entry.step)}@${String(entry.at)}`);
1630
1630
  if (lost.length > 0) {
1631
- refuse("drive.reload.history", "drive.reload.history", `a second adapter built from the same login carries ${taskId} without ${lost.join(", ")}, which the first had published: a record once read is not unread`);
1631
+ refuse("test.reload.history", "test.reload.history", `a second adapter built from the same login carries ${taskId} without ${lost.join(", ")}, which the first had published: a record once read is not unread`);
1632
1632
  }
1633
1633
  const leg = steps.find(entry => isRecord(entry) && entry.step === "muted" && entry.at === at);
1634
1634
  if (leg === undefined) {
1635
- refuse("drive.reload.history", "drive.reload.history", `a second adapter built from the same login carries ${taskId} without the muted leg at ${at}: the record was composed in memory and died with the adapter; the login's store is where it lives`);
1635
+ refuse("test.reload.history", "test.reload.history", `a second adapter built from the same login carries ${taskId} without the muted leg at ${at}: the record was composed in memory and died with the adapter; the login's store is where it lives`);
1636
1636
  }
1637
1637
  else if (leg.mutedBy !== "host") {
1638
- refuse("drive.reload.history", "drive.reload.history", `the reloaded record's muted leg at ${at} says mutedBy ${String(leg.mutedBy)}; the host reported host`);
1638
+ refuse("test.reload.history", "test.reload.history", `the reloaded record's muted leg at ${at} says mutedBy ${String(leg.mutedBy)}; the host reported host`);
1639
1639
  }
1640
- // The drive goes on with the task as the reloaded client holds it. The first client's audio
1640
+ // The test goes on with the task as the reloaded client holds it. The first client's audio
1641
1641
  // session died with it: a host opens audio on a task arriving with audio started on a
1642
- // snapshot, so the drive opens it again on the second and holds the answer as it held the first.
1642
+ // snapshot, so the test opens it again on the second and holds the answer as it held the first.
1643
1643
  task = carried;
1644
- if (drive.softphone && carried.audio === "started") {
1644
+ if (call.softphone && carried.audio === "started") {
1645
1645
  if (session !== undefined && typeof session.close === "function") {
1646
1646
  try {
1647
1647
  session.close();
1648
1648
  }
1649
1649
  catch {
1650
- refuse("drive.openAudio.close", "drive.openAudio", "the first client's audio session threw on close");
1650
+ refuse("test.openAudio.close", "test.openAudio", "the first client's audio session threw on close");
1651
1651
  }
1652
1652
  }
1653
1653
  session = undefined;
1654
- ruleEvaluated("drive.reload.openAudio");
1655
- const reopened = await second.openAudio?.({ assignmentId: taskId, localAudio: drive.localAudio });
1656
- const malformed = validateResult(reopened, "openAudio", "drive.reload.openAudio");
1654
+ ruleTested("test.reload.openAudio");
1655
+ const reopened = await second.openAudio?.({ assignmentId: taskId, localAudio: call.localAudio });
1656
+ const malformed = validateResult(reopened, "openAudio", "test.reload.openAudio");
1657
1657
  found.push(...malformed);
1658
1658
  if (malformed.length === 0 && isRecord(reopened) && reopened.status === "opened")
1659
1659
  session = reopened.audio;
1660
1660
  else if (malformed.length === 0)
1661
- refuse("drive.reload.openAudio", "drive.reload.openAudio", `a second adapter built from the same login could not open the audio of ${taskId}, which its own snapshot carries with audio started`);
1661
+ refuse("test.reload.openAudio", "test.reload.openAudio", `a second adapter built from the same login could not open the audio of ${taskId}, which its own snapshot carries with audio started`);
1662
1662
  }
1663
1663
  }
1664
1664
  catch (error) {
1665
- refuse("drive.reload.rejected", "drive.reload", `the second adapter rejected rather than answered: ${String(error)}`);
1665
+ refuse("test.reload.rejected", "test.reload", `the second adapter rejected rather than answered: ${String(error)}`);
1666
1666
  }
1667
1667
  };
1668
1668
  // A task's keys are gone with the task. A task-scoped key carries the task id, so the watched
@@ -1675,28 +1675,28 @@ async function driveOneCall(drive) {
1675
1675
  // A task's key names the task delimited, not as a run of characters inside another id, and the
1676
1676
  // rule is evaluated only where the store was ever seen to hold such a key: an adapter whose keys
1677
1677
  // name nothing the harness can see leaves the rule unasked, which the result says.
1678
- const retained = [...drive.held].filter(key => namesTask(key, taskId));
1678
+ const retained = [...call.held].filter(key => namesTask(key, taskId));
1679
1679
  retainedAtEnd = new Set(retained);
1680
- if (drive.everHeldForTask(taskId))
1681
- ruleEvaluated("drive.store.retained");
1680
+ if (call.everHeldForTask(taskId))
1681
+ ruleTested("test.store.retained");
1682
1682
  if (retained.length > 0) {
1683
- refuse("drive.store.retained", "drive.store", `${taskId} has ended and the login's store still holds ${retained.join(", ")}: a task's keys go with the task, or the next offer of the same id inherits them`);
1683
+ refuse("test.store.retained", "test.store", `${taskId} has ended and the login's store still holds ${retained.join(", ")}: a task's keys go with the task, or the next offer of the same id inherits them`);
1684
1684
  }
1685
1685
  };
1686
1686
  // A key written about the task after its end -- a persist hung off a timer, a late command -- is the
1687
- // same hazard arriving later, so the store is read once more when the drive is done.
1687
+ // same hazard arriving later, so the store is read once more when the test is done.
1688
1688
  const nothingLate = () => {
1689
1689
  if (retainedAtEnd === undefined)
1690
1690
  return;
1691
- ruleEvaluated("drive.store.late");
1692
- const late = [...drive.held].filter(key => namesTask(key, taskId) && !retainedAtEnd.has(key));
1691
+ ruleTested("test.store.late");
1692
+ const late = [...call.held].filter(key => namesTask(key, taskId) && !retainedAtEnd.has(key));
1693
1693
  if (late.length > 0) {
1694
- refuse("drive.store.late", "drive.store", `${taskId} had ended with its keys gone, and the login's store now holds ${late.join(", ")}: something wrote about the task after its end`);
1694
+ refuse("test.store.late", "test.store", `${taskId} had ended with its keys gone, and the login's store now holds ${late.join(", ")}: something wrote about the task after its end`);
1695
1695
  }
1696
1696
  };
1697
1697
  // The task, as first seen: on the snapshot, or on the first offer.
1698
1698
  let cursor = 0;
1699
- let task = isRecord(drive.snapshot) && Array.isArray(drive.snapshot.tasks) ? drive.snapshot.tasks.find(isTask) : undefined;
1699
+ let task = isRecord(call.snapshot) && Array.isArray(call.snapshot.tasks) ? call.snapshot.tasks.find(isTask) : undefined;
1700
1700
  if (task === undefined) {
1701
1701
  const offered = await waitFor("a task-offered", envelope => {
1702
1702
  const event = envelope.event;
@@ -1718,35 +1718,35 @@ async function driveOneCall(drive) {
1718
1718
  return undefined;
1719
1719
  }, cursor, within).then(hit => { if (hit)
1720
1720
  cursor = hit.at; return hit; });
1721
- const ended = (within) => waitFor("a task-ended for the driven task", envelope => {
1721
+ const ended = (within) => waitFor("a task-ended for the tested task", envelope => {
1722
1722
  const event = envelope.event;
1723
1723
  return event.type === "task-ended" && event.assignmentId === taskId ? event : undefined;
1724
1724
  }, cursor, within);
1725
- // Every command the drive sends is validated against the task as published, and its answer for its method.
1726
- ruleEvaluated("drive.timeout");
1725
+ // Every command the test sends is validated against the task as published, and its answer for its method.
1726
+ ruleTested("test.timeout");
1727
1727
  const send = async (command, dialId) => {
1728
- const own = validateTaskCommand(command, latestTask(), `drive.command.${String(command.type)}`, {
1729
- levels: effectiveLevels(drive.manifest.orgLevels).map(level => level.id),
1730
- dialOutcomesDeclared: drive.manifest.dialOutcomes !== undefined,
1731
- calendarDeclared: drive.manifest.idleCapabilities?.calendar === true,
1732
- autoAcceptTasks: drive.context.autoAcceptTasks,
1728
+ const own = validateTaskCommand(command, latestTask(), `test.command.${String(command.type)}`, {
1729
+ levels: effectiveLevels(call.manifest.orgLevels).map(level => level.id),
1730
+ dialOutcomesDeclared: call.manifest.dialOutcomes !== undefined,
1731
+ calendarDeclared: call.manifest.idleCapabilities?.calendar === true,
1732
+ autoAcceptTasks: call.context.autoAcceptTasks,
1733
1733
  });
1734
1734
  found.push(...own);
1735
1735
  if (own.length > 0)
1736
1736
  return undefined;
1737
- ruleEvaluated("drive.command.rejected");
1737
+ ruleTested("test.command.rejected");
1738
1738
  let result;
1739
1739
  try {
1740
- result = await drive.connection.execute({ assignmentId: taskId, command });
1740
+ result = await call.connection.execute({ assignmentId: taskId, command });
1741
1741
  }
1742
1742
  catch (error) {
1743
- refuse("drive.command.rejected", `drive.command.${String(command.type)}`, `execute rejected rather than answered: ${String(error)}`);
1743
+ refuse("test.command.rejected", `test.command.${String(command.type)}`, `execute rejected rather than answered: ${String(error)}`);
1744
1744
  return undefined;
1745
1745
  }
1746
- ruleEvaluated("drive.command.failed");
1747
- found.push(...validateResult(result, "execute", `drive.command.${String(command.type)}.result`, dialId));
1746
+ ruleTested("test.command.failed");
1747
+ found.push(...validateResult(result, "execute", `test.command.${String(command.type)}.result`, dialId));
1748
1748
  if (isRecord(result) && result.status === "failed") {
1749
- refuse("drive.command.failed", `drive.command.${String(command.type)}`, `the provider refused ${String(command.type)} on a task that offered it: ${String(isRecord(result.failure) ? result.failure.code : result.failure)}`);
1749
+ refuse("test.command.failed", `test.command.${String(command.type)}`, `the provider refused ${String(command.type)} on a task that offered it: ${String(isRecord(result.failure) ? result.failure.code : result.failure)}`);
1750
1750
  return undefined;
1751
1751
  }
1752
1752
  return isRecord(result) ? result : undefined;
@@ -1758,15 +1758,15 @@ async function driveOneCall(drive) {
1758
1758
  };
1759
1759
  // Audio may arrive any time after the accept, before or after the task's own update says
1760
1760
  // in-progress: the event is the provider's word that the audio should attach, never a reply to
1761
- // openAudio, so the drive looks for it from the accept rather than from the last update it read.
1761
+ // openAudio, so the test looks for it from the accept rather than from the last update it read.
1762
1762
  // 0. A deadline stated is a deadline kept. Where the first offer says how long it has, within
1763
- // what the drive will wait, the drive leaves it alone and expects the provider to end it expired
1764
- // when it runs out; the next offer is the one driven. Offers with no deadline, or a longer one,
1763
+ // what the test will wait, the test leaves it alone and expects the provider to end it expired
1764
+ // when it runs out; the next offer is the one tested. Offers with no deadline, or a longer one,
1765
1765
  // leave this rule unreached, and the result says so.
1766
- const settleMs = Number(drive.manifest.settleMs);
1766
+ const settleMs = Number(call.manifest.settleMs);
1767
1767
  const lapse = latestTask().phase === "pending" ? latestTask().expiresInSeconds : undefined;
1768
- if (typeof lapse === "number" && lapse * 1000 <= drive.timeoutMs) {
1769
- ruleEvaluated("drive.offer.expired");
1768
+ if (typeof lapse === "number" && lapse * 1000 <= call.timeoutMs) {
1769
+ ruleTested("test.offer.expired");
1770
1770
  const lapsedId = taskId;
1771
1771
  let missed = false;
1772
1772
  const ending = await waitFor(`the expired ending of ${lapsedId}`, envelope => {
@@ -1775,14 +1775,14 @@ async function driveOneCall(drive) {
1775
1775
  }, cursor, { ms: lapse * 1000 + settleMs, onExpiry: () => { missed = true; } });
1776
1776
  if (ending === undefined) {
1777
1777
  if (missed)
1778
- refuse("drive.offer.expired", "drive.offer", `${lapsedId} was offered with ${lapse}s to answer and ${lapse * 1000 + settleMs}ms later it has not ended: a deadline stated is a deadline kept, and the offer ends expired naming pending`);
1778
+ refuse("test.offer.expired", "test.offer", `${lapsedId} was offered with ${lapse}s to answer and ${lapse * 1000 + settleMs}ms later it has not ended: a deadline stated is a deadline kept, and the offer ends expired naming pending`);
1779
1779
  return found;
1780
1780
  }
1781
1781
  const outcome = isRecord(ending.found.outcome) ? ending.found.outcome : {};
1782
1782
  if (!(outcome.type === "expired" && outcome.phase === "pending")) {
1783
- refuse("drive.offer.expired", "drive.offer", `${lapsedId} lapsed unanswered and ended ${String(outcome.type)}: an offer nobody answered before its deadline ends expired naming pending`);
1783
+ refuse("test.offer.expired", "test.offer", `${lapsedId} lapsed unanswered and ended ${String(outcome.type)}: an offer nobody answered before its deadline ends expired naming pending`);
1784
1784
  }
1785
- // The next offer is the one driven; a provider with nothing more to offer ends the drive here.
1785
+ // The next offer is the one tested; a provider with nothing more to offer ends the test here.
1786
1786
  const next = await waitFor("a task-offered after the lapsed one", envelope => {
1787
1787
  const event = envelope.event;
1788
1788
  return event.type === "task-offered" && isTask(event.task) && event.task.assignmentId !== lapsedId ? event.task : undefined;
@@ -1793,25 +1793,25 @@ async function driveOneCall(drive) {
1793
1793
  cursor = next.at;
1794
1794
  taskId = task.assignmentId;
1795
1795
  }
1796
- const acceptedAt = drive.events.length;
1796
+ const acceptedAt = call.events.length;
1797
1797
  // 1. Accept the offer, if it is one.
1798
1798
  if (latestTask().phase === "pending") {
1799
- if (await send({ type: drive.channel === "voice" ? "answer" : "accept" }) === undefined)
1799
+ if (await send({ type: call.channel === "voice" ? "answer" : "accept" }) === undefined)
1800
1800
  return found;
1801
1801
  if (await updated(t => t.phase !== "pending", "the task leaving pending after it was accepted") === undefined)
1802
1802
  return found;
1803
1803
  }
1804
1804
  // 2. A preview: press Call, which is a dial. Where the preview says how long it has, within what
1805
- // the drive will wait, the deadline is kept to its atDeadline first: under provider-dials the
1806
- // provider dials when it runs out and the drive presses nothing; under host-dials and waits the
1807
- // preview stands until then, and the drive presses Call at zero as the desk would.
1805
+ // the test will wait, the deadline is kept to its atDeadline first: under provider-dials the
1806
+ // provider dials when it runs out and the test presses nothing; under host-dials and waits the
1807
+ // preview stands until then, and the test presses Call at zero as the desk would.
1808
1808
  if (latestTask().phase === "preview") {
1809
1809
  const left = latestTask().previewEndsInSeconds;
1810
1810
  const atDeadline = latestTask().atDeadline;
1811
1811
  // Under provider-dials the provider places the call, and the desk presses nothing.
1812
1812
  let pressCall = true;
1813
- if (typeof left === "number" && left * 1000 <= drive.timeoutMs && typeof atDeadline === "string") {
1814
- ruleEvaluated("drive.preview.deadline");
1813
+ if (typeof left === "number" && left * 1000 <= call.timeoutMs && typeof atDeadline === "string") {
1814
+ ruleTested("test.preview.deadline");
1815
1815
  const ringing = (t) => t.phase !== "preview"
1816
1816
  || (Array.isArray(t.onCall) && t.onCall.some(who => isRecord(who) && who.role === "party" && who.stage === "ringing"));
1817
1817
  if (atDeadline === "provider-dials") {
@@ -1820,24 +1820,24 @@ async function driveOneCall(drive) {
1820
1820
  if (dialled === undefined) {
1821
1821
  if (!missed)
1822
1822
  return found;
1823
- refuse("drive.preview.deadline", "drive.preview", `${taskId} is a preview under provider-dials with ${left}s left, and the provider did not dial when it ran out: the party rings on onCall, or the task leaves preview`);
1823
+ refuse("test.preview.deadline", "test.preview", `${taskId} is a preview under provider-dials with ${left}s left, and the provider did not dial when it ran out: the party rings on onCall, or the task leaves preview`);
1824
1824
  return found;
1825
1825
  }
1826
1826
  pressCall = false;
1827
1827
  }
1828
1828
  else {
1829
- // Nothing may move the preview before its deadline under host-dials or waits: the drive
1829
+ // Nothing may move the preview before its deadline under host-dials or waits: the test
1830
1830
  // waits the seconds out and expects the task where it left it.
1831
1831
  const moved = await updated(t => t.phase !== "preview", "nothing", { ms: left * 1000, onExpiry: () => undefined });
1832
1832
  if (moved !== undefined) {
1833
- refuse("drive.preview.deadline", "drive.preview", `${taskId} is a preview under ${atDeadline} with ${left}s left, and the provider moved it to ${String(moved.found.phase)} before the deadline: under ${atDeadline} the preview stands until the desk dials, or for as long as the agent needs`);
1833
+ refuse("test.preview.deadline", "test.preview", `${taskId} is a preview under ${atDeadline} with ${left}s left, and the provider moved it to ${String(moved.found.phase)} before the deadline: under ${atDeadline} the preview stands until the desk dials, or for as long as the agent needs`);
1834
1834
  return found;
1835
1835
  }
1836
1836
  }
1837
1837
  }
1838
1838
  if (pressCall && latestTask().phase === "preview") {
1839
- const dialId = `drive-${taskId}`;
1840
- drive.stream.dialled(dialId);
1839
+ const dialId = `test-${taskId}`;
1840
+ call.stream.dialled(dialId);
1841
1841
  if (await send({ type: "dial", dialId }, dialId) === undefined)
1842
1842
  return found;
1843
1843
  }
@@ -1846,19 +1846,19 @@ async function driveOneCall(drive) {
1846
1846
  }
1847
1847
  // The other direction of step 4, wherever the task stands outside the interaction phases with the
1848
1848
  // control still declared: a host holds it back (command.phase.interaction), and an adapter that
1849
- // receives it anyway must refuse it -- so the drive sends it past the validator and expects failed.
1849
+ // receives it anyway must refuse it -- so the test sends it past the validator and expects failed.
1850
1850
  const holdRefusedOutsideInteraction = async () => {
1851
- ruleEvaluated("drive.command.rejected", "drive.command.interaction");
1851
+ ruleTested("test.command.rejected", "test.command.interaction");
1852
1852
  const phase = String(latestTask().phase);
1853
1853
  let answer;
1854
1854
  try {
1855
- answer = await drive.connection.execute({ assignmentId: taskId, command: { type: "hold" } });
1855
+ answer = await call.connection.execute({ assignmentId: taskId, command: { type: "hold" } });
1856
1856
  }
1857
1857
  catch (error) {
1858
- refuse("drive.command.rejected", "drive.command.hold", `execute rejected rather than answered: ${String(error)}`);
1858
+ refuse("test.command.rejected", "test.command.hold", `execute rejected rather than answered: ${String(error)}`);
1859
1859
  }
1860
1860
  if (isRecord(answer) && answer.status !== "failed") {
1861
- refuse("drive.command.interaction", "drive.command.hold", `the provider applied hold on a ${phase} task: a control on the contact belongs to in-progress or paused, and the adapter is the second gate`);
1861
+ refuse("test.command.interaction", "test.command.hold", `the provider applied hold on a ${phase} task: a control on the contact belongs to in-progress or paused, and the adapter is the second gate`);
1862
1862
  }
1863
1863
  };
1864
1864
  if (latestTask().phase === "confirmed") {
@@ -1868,9 +1868,9 @@ async function driveOneCall(drive) {
1868
1868
  return found;
1869
1869
  }
1870
1870
  // 3. On a softphone, the audio arrives and the host opens it.
1871
- if (drive.softphone && latestTask().phase === "in-progress") {
1871
+ if (call.softphone && latestTask().phase === "in-progress") {
1872
1872
  if (latestTask().audio !== "started") {
1873
- const started = await waitFor("task-audio-started for the driven task", envelope => {
1873
+ const started = await waitFor("task-audio-started for the tested task", envelope => {
1874
1874
  const event = envelope.event;
1875
1875
  return event.type === "task-audio-started" && event.assignmentId === taskId ? event : undefined;
1876
1876
  }, acceptedAt);
@@ -1878,15 +1878,15 @@ async function driveOneCall(drive) {
1878
1878
  return found;
1879
1879
  cursor = Math.max(cursor, started.at);
1880
1880
  }
1881
- const opened = await drive.connection.openAudio?.({ assignmentId: taskId, localAudio: drive.localAudio });
1882
- const malformed = validateResult(opened, "openAudio", "drive.openAudio");
1881
+ const opened = await call.connection.openAudio?.({ assignmentId: taskId, localAudio: call.localAudio });
1882
+ const malformed = validateResult(opened, "openAudio", "test.openAudio");
1883
1883
  found.push(...malformed);
1884
1884
  if (malformed.length === 0 && isRecord(opened) && opened.status === "opened")
1885
1885
  session = opened.audio;
1886
1886
  else if (malformed.length === 0 && isRecord(opened))
1887
- refuse("drive.openAudio.unavailable", "drive.openAudio", "a softphone login's adapter could not open the call's audio");
1887
+ refuse("test.openAudio.unavailable", "test.openAudio", "a softphone login's adapter could not open the call's audio");
1888
1888
  }
1889
- // 3b. The microphone is the host's. With the audio open, the drive mutes it for a moment and
1889
+ // 3b. The microphone is the host's. With the audio open, the test mutes it for a moment and
1890
1890
  // reports the leg the provider's record would otherwise miss -- begun, then ended -- and
1891
1891
  // expects each report recorded. If the provider restates the task's record afterwards, the leg is in it.
1892
1892
  const canonicalTimes = new Map();
@@ -1898,34 +1898,34 @@ async function driveOneCall(drive) {
1898
1898
  ? history.steps.find(entry => isRecord(entry) && entry.step === "muted" && entry.at === canonicalTimes.get(at)) : undefined;
1899
1899
  return typeof leg?.seconds === "number" ? leg.seconds : undefined;
1900
1900
  };
1901
- const canRecordMute = session !== undefined && typeof session.setMuted === "function" && typeof drive.connection.recordStep === "function";
1901
+ const canRecordMute = session !== undefined && typeof session.setMuted === "function" && typeof call.connection.recordStep === "function";
1902
1902
  const report = async (body) => {
1903
- ruleEvaluated("drive.recordStep.rejected", "drive.recordStep.failed");
1904
- // The drive holds its own report to the contract before it crosses, as a host must.
1903
+ ruleTested("test.recordStep.rejected", "test.recordStep.failed");
1904
+ // The test holds its own report to the contract before it crosses, as a host must.
1905
1905
  const leg = { assignmentId: taskId, step: "muted", mutedBy: "host", ...body };
1906
- const own = validateHistoryReport(leg, "drive.recordStep.report", drive.manifest);
1906
+ const own = validateHistoryReport(leg, "test.recordStep.report", call.manifest);
1907
1907
  found.push(...own);
1908
1908
  if (own.length > 0)
1909
1909
  return;
1910
1910
  let answer;
1911
1911
  try {
1912
- answer = await drive.connection.recordStep(leg);
1912
+ answer = await call.connection.recordStep(leg);
1913
1913
  }
1914
1914
  catch (error) {
1915
- refuse("drive.recordStep.rejected", "drive.recordStep", `recordStep rejected rather than answered: ${String(error)}`);
1915
+ refuse("test.recordStep.rejected", "test.recordStep", `recordStep rejected rather than answered: ${String(error)}`);
1916
1916
  return;
1917
1917
  }
1918
- const resultErrors = validateResult(answer, "recordStep", "drive.recordStep.result");
1918
+ const resultErrors = validateResult(answer, "recordStep", "test.recordStep.result");
1919
1919
  found.push(...resultErrors);
1920
1920
  if (!resultErrors.length && isRecord(answer) && answer.status === "recorded" && typeof answer.at === "string" && typeof body.at === "string") {
1921
1921
  const prior = canonicalTimes.get(body.at);
1922
1922
  if (prior !== undefined && prior !== answer.at)
1923
- refuse("drive.recordStep.identity", "drive.recordStep.result.at", "one reported leg must retain its provider-assigned history instant");
1923
+ refuse("test.recordStep.identity", "test.recordStep.result.at", "one reported leg must retain its provider-assigned history instant");
1924
1924
  else
1925
1925
  canonicalTimes.set(body.at, answer.at);
1926
1926
  }
1927
1927
  if (isRecord(answer) && answer.status === "failed") {
1928
- refuse("drive.recordStep.failed", "drive.recordStep", `the provider refused to record the host's muted leg: ${String(isRecord(answer.failure) ? answer.failure.code : answer.failure)}`);
1928
+ refuse("test.recordStep.failed", "test.recordStep", `the provider refused to record the host's muted leg: ${String(isRecord(answer.failure) ? answer.failure.code : answer.failure)}`);
1929
1929
  }
1930
1930
  };
1931
1931
  const setMuted = (muted) => {
@@ -1936,7 +1936,7 @@ async function driveOneCall(drive) {
1936
1936
  session.setMuted(muted);
1937
1937
  }
1938
1938
  catch {
1939
- refuse("drive.openAudio.setMuted", "drive.openAudio", `the audio session threw on setMuted(${String(muted)})`);
1939
+ refuse("test.openAudio.setMuted", "test.openAudio", `the audio session threw on setMuted(${String(muted)})`);
1940
1940
  }
1941
1941
  };
1942
1942
  if (canRecordMute) {
@@ -1952,16 +1952,16 @@ async function driveOneCall(drive) {
1952
1952
  // 3c. A host reload destroys the adapter object and keeps the login's store. Built again from
1953
1953
  // the same login, a second adapter carries the task and the leg -- from its platform, or from
1954
1954
  // the store -- or it composed the record in memory and the record died with it.
1955
- if (drive.rebuild !== undefined && canonicalTimes.has(at)) {
1955
+ if (call.rebuild !== undefined && canonicalTimes.has(at)) {
1956
1956
  await recordSurvivesReload(canonicalTimes.get(at));
1957
- // A reload that stood no client ends the run: there is nothing left to drive.
1958
- if (!drive.handOver.isLive())
1957
+ // A reload that stood no client ends the run: there is nothing left to call.
1958
+ if (!call.handOver.isLive())
1959
1959
  return found;
1960
1960
  }
1961
1961
  }
1962
1962
  // 4. Hold and resume, where offered.
1963
1963
  if (latestTask().phase === "in-progress" && offers("hold")) {
1964
- if (await send({ type: drive.channel === "chat" ? "pause" : "hold" }) !== undefined) {
1964
+ if (await send({ type: call.channel === "chat" ? "pause" : "hold" }) !== undefined) {
1965
1965
  if (await updated(t => t.phase === "paused", "the task pausing after hold") !== undefined) {
1966
1966
  if (await send({ type: "resume" }) !== undefined) {
1967
1967
  await updated(t => t.phase === "in-progress", "the task resuming after resume");
@@ -1973,14 +1973,14 @@ async function driveOneCall(drive) {
1973
1973
  // calendar-updated that shows it is owed within the manifest's bound. The time is the provider's
1974
1974
  // own: a day after the instant it published the offer, so no clock of the runner's is read.
1975
1975
  if (latestTask().phase === "in-progress" && offers("schedule")) {
1976
- const publishedAt = drive.events.slice(0, cursor).reverse().find(envelope => {
1976
+ const publishedAt = call.events.slice(0, cursor).reverse().find(envelope => {
1977
1977
  const event = envelope.event;
1978
1978
  return (event.type === "task-offered" || event.type === "task-updated") && isTask(event.task) && event.task.assignmentId === taskId;
1979
1979
  })?.occurredAt;
1980
1980
  const at = new Date(Date.parse(String(publishedAt ?? "2026-01-01T00:00:00Z")) + 24 * 60 * 60 * 1000).toISOString();
1981
1981
  if (await send({ type: "schedule", at, note: "Follow-up promised on the call" }) !== undefined) {
1982
- ruleEvaluated("drive.schedule.unsettled");
1983
- const settle = Number(drive.manifest.settleMs);
1982
+ ruleTested("test.schedule.unsettled");
1983
+ const settle = Number(call.manifest.settleMs);
1984
1984
  const carries = (calendar) => Array.isArray(calendar) && calendar.some(activity => isRecord(activity) && activity.startsAt === at);
1985
1985
  let unsettled = false;
1986
1986
  const shown = await waitFor("a calendar-updated carrying the follow-up", envelope => {
@@ -1990,13 +1990,13 @@ async function driveOneCall(drive) {
1990
1990
  if (shown === undefined && unsettled) {
1991
1991
  let resync;
1992
1992
  try {
1993
- resync = await drive.connection.snapshot();
1993
+ resync = await call.connection.snapshot();
1994
1994
  }
1995
1995
  catch (error) {
1996
- refuse("drive.command.rejected", "drive.schedule", `snapshot() after an unsettled schedule rejected: ${String(error)}`);
1996
+ refuse("test.command.rejected", "test.schedule", `snapshot() after an unsettled schedule rejected: ${String(error)}`);
1997
1997
  }
1998
1998
  if (!(isRecord(resync) && carries(resync.calendar))) {
1999
- refuse("drive.schedule.unsettled", "drive.schedule", `schedule was applied and ${settle}ms later no calendar-updated carries a follow-up at ${at}, and a snapshot does not either: applied says the follow-up is on the calendar, and it is not`);
1999
+ refuse("test.schedule.unsettled", "test.schedule", `schedule was applied and ${settle}ms later no calendar-updated carries a follow-up at ${at}, and a snapshot does not either: applied says the follow-up is on the calendar, and it is not`);
2000
2000
  }
2001
2001
  }
2002
2002
  }
@@ -2004,9 +2004,9 @@ async function driveOneCall(drive) {
2004
2004
  // 5. End the call, where the agent may -- with the microphone muted, as agents do. The leg left
2005
2005
  // open at audio end is the provider's to close, since the provider knows the instant the audio
2006
2006
  // ended; the host's own closing report can only follow what it hears, is answered recorded, and
2007
- // changes nothing. So the drive mutes, does not report the end before end-call, and reports it
2007
+ // changes nothing. So the test mutes, does not report the end before end-call, and reports it
2008
2008
  // after the audio has ended.
2009
- if (drive.channel === "voice" && latestTask().phase === "in-progress" && offers("endCall")) {
2009
+ if (call.channel === "voice" && latestTask().phase === "in-progress" && offers("endCall")) {
2010
2010
  let openLeg;
2011
2011
  if (canRecordMute && session !== undefined) {
2012
2012
  // The task as published before the call ends: the completing publication that follows is the restatement held to carrying this leg.
@@ -2016,7 +2016,7 @@ async function driveOneCall(drive) {
2016
2016
  }
2017
2017
  if (await send({ type: "end-call" }) !== undefined) {
2018
2018
  // A task that completes with its audio still up is refused by the stream as it passes
2019
- // (stream.taskUpdated.audioOpen); the drive does not also wait out the clock for an ending
2019
+ // (stream.taskUpdated.audioOpen); the test does not also wait out the clock for an ending
2020
2020
  // that is not coming, so the run reads by the rule and not by its timeout.
2021
2021
  const audioEnded = await waitFor("task-audio-ended after end-call", envelope => {
2022
2022
  const event = envelope.event;
@@ -2048,11 +2048,11 @@ async function driveOneCall(drive) {
2048
2048
  session.close();
2049
2049
  }
2050
2050
  catch {
2051
- refuse("drive.openAudio.close", "drive.openAudio", "the audio session threw on close");
2051
+ refuse("test.openAudio.close", "test.openAudio", "the audio session threw on close");
2052
2052
  }
2053
2053
  }
2054
2054
  // 6. Complete, under either mode: the agent may finish early and the provider is free to end at
2055
- // once. Only a task with no wrap at all has nothing to complete, and there the drive waits for the
2055
+ // once. Only a task with no wrap at all has nothing to complete, and there the test waits for the
2056
2056
  // provider. A conversation has no audio to end and no completing phase to wait for: a chat or an
2057
2057
  // email, and a voice task offering no end-call, is completed from where it stands.
2058
2058
  const completable = latestTask().phase === "completing" || latestTask().phase === "in-progress" || latestTask().phase === "paused";
@@ -2066,8 +2066,8 @@ async function driveOneCall(drive) {
2066
2066
  // applied says the provider has completed the task, and its ending follows within the bound
2067
2067
  // the manifest stated. Past it the host resyncs: a snapshot still carrying the task is a task
2068
2068
  // held open by a provider that said it was done, and the desk shows it as unsettled.
2069
- ruleEvaluated("drive.completion.unsettled");
2070
- const settle = Number(drive.manifest.settleMs);
2069
+ ruleTested("test.completion.unsettled");
2070
+ const settle = Number(call.manifest.settleMs);
2071
2071
  let unsettled = false;
2072
2072
  const end = await ended({ ms: settle, onExpiry: () => { unsettled = true; } });
2073
2073
  if (end !== undefined)
@@ -2075,13 +2075,13 @@ async function driveOneCall(drive) {
2075
2075
  else if (unsettled) {
2076
2076
  let resync;
2077
2077
  try {
2078
- resync = await drive.connection.snapshot();
2078
+ resync = await call.connection.snapshot();
2079
2079
  }
2080
2080
  catch (error) {
2081
- refuse("drive.command.rejected", "drive.completion", `snapshot() after an unsettled completion rejected: ${String(error)}`);
2081
+ refuse("test.command.rejected", "test.completion", `snapshot() after an unsettled completion rejected: ${String(error)}`);
2082
2082
  }
2083
2083
  const still = isRecord(resync) && Array.isArray(resync.tasks) && resync.tasks.some(t => isRecord(t) && t.assignmentId === taskId);
2084
- refuse("drive.completion.unsettled", "drive.completion", still
2084
+ refuse("test.completion.unsettled", "test.completion", still
2085
2085
  ? `complete was applied and ${settle}ms later the task-ended has not come and a snapshot still carries ${taskId}: applied says the provider completed the task, and it has not`
2086
2086
  : `complete was applied and ${settle}ms later the task-ended has not come; a snapshot no longer carries ${taskId}, so the ending was owed and never sent`);
2087
2087
  }
@@ -2092,10 +2092,10 @@ async function driveOneCall(drive) {
2092
2092
  await storeReleased();
2093
2093
  }
2094
2094
  // The record kept by the provider has every leg the host reported, wherever the provider restated it:
2095
- // the last publication of the task is read, whether or not the drive was waiting on it.
2095
+ // the last publication of the task is read, whether or not the test was waiting on it.
2096
2096
  const lastPublished = () => {
2097
- for (let index = drive.events.length - 1; index >= 0; index -= 1) {
2098
- const event = drive.events[index].event;
2097
+ for (let index = call.events.length - 1; index >= 0; index -= 1) {
2098
+ const event = call.events[index].event;
2099
2099
  if ((event.type === "task-updated" || event.type === "task-offered") && isTask(event.task) && event.task.assignmentId === taskId)
2100
2100
  return event.task;
2101
2101
  }
@@ -2112,15 +2112,15 @@ async function driveOneCall(drive) {
2112
2112
  const { at } = reported;
2113
2113
  const leg = history.steps.find(entry => isRecord(entry) && entry.step === "muted" && entry.at === at);
2114
2114
  if (leg === undefined) {
2115
- refuse("drive.recordStep.history", "drive.recordStep", `the provider restated the task's record after the host reported a muted leg at ${at}, and the leg is not in it`);
2115
+ refuse("test.recordStep.history", "test.recordStep", `the provider restated the task's record after the host reported a muted leg at ${at}, and the leg is not in it`);
2116
2116
  }
2117
2117
  else if (leg.mutedBy !== "host") {
2118
- refuse("drive.recordStep.history", "drive.recordStep", `the record's muted leg at ${at} says mutedBy ${String(leg.mutedBy)}; the host reported host, and the record keeps the host's word`);
2118
+ refuse("test.recordStep.history", "test.recordStep", `the record's muted leg at ${at} says mutedBy ${String(leg.mutedBy)}; the host reported host, and the record keeps the host's word`);
2119
2119
  }
2120
2120
  else if (reported.closedAs !== undefined) {
2121
- ruleEvaluated("drive.recordStep.overwritten");
2121
+ ruleTested("test.recordStep.overwritten");
2122
2122
  if (leg.seconds !== reported.closedAs) {
2123
- refuse("drive.recordStep.overwritten", "drive.recordStep", `the provider closed the leg at ${at} with ${reported.closedAs}s at audio end and the record now says ${String(leg.seconds)}s: the host's closing report changes nothing`);
2123
+ refuse("test.recordStep.overwritten", "test.recordStep", `the provider closed the leg at ${at} with ${reported.closedAs}s at audio end and the record now says ${String(leg.seconds)}s: the host's closing report changes nothing`);
2124
2124
  }
2125
2125
  }
2126
2126
  }
@@ -2169,7 +2169,7 @@ export class TeamStream {
2169
2169
  case "team-member-updated":
2170
2170
  case "team-member-removed":
2171
2171
  case "team-policies-updated": {
2172
- ruleEvaluated("stream.team.baseline");
2172
+ ruleTested("stream.team.baseline");
2173
2173
  if (this.members === undefined) {
2174
2174
  return [{ rule: "stream.team.baseline", path: `${at}.type`,
2175
2175
  message: `${event.type} arrived before the team: team-updated carries the whole team first, and it changes one member at a time after that` }];
@@ -2180,7 +2180,7 @@ export class TeamStream {
2180
2180
  return [];
2181
2181
  }
2182
2182
  if (event.type === "team-member-removed") {
2183
- ruleEvaluated("stream.teamMember.unknown");
2183
+ ruleTested("stream.teamMember.unknown");
2184
2184
  const id = String(event.memberId);
2185
2185
  if (!this.members.has(id)) {
2186
2186
  return [{ rule: "stream.teamMember.unknown", path: `${at}.memberId`, message: `${id} is not a member the team carried: a removal takes off somebody the lead was given` }];
@@ -2273,7 +2273,7 @@ function watchStore(store) {
2273
2273
  },
2274
2274
  };
2275
2275
  }
2276
- /** A login store that lives in memory for the test: what most adapter tests hand `exerciseAdapter` as `context.store`. */
2276
+ /** A login store that lives in memory for the test: what most adapter tests hand `testAdapter` as `context.store`. */
2277
2277
  export function memoryStore() {
2278
2278
  const kept = new Map();
2279
2279
  return {