@xema/omni-protocol 0.1.99 → 0.1.100

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.
@@ -41,7 +41,7 @@ const TASK_PHASES = membersOf({
41
41
  pending: true, confirmed: true, preview: true, "in-progress": true, paused: true, completing: true,
42
42
  });
43
43
  const COMPLETION_MODES = membersOf({ "agent-command": true, "provider-automatic": true });
44
- const CAPABILITY_SOURCES = membersOf({ queue: true, ungoverned: true, undetermined: true });
44
+ const CAPABILITY_SOURCES = membersOf({ queue: true, nobody: true, "not-yet-read": true });
45
45
  const ACCEPTANCE_MODES = membersOf({
46
46
  "no-preference": true, "consent": true, "automatic": true,
47
47
  });
@@ -55,7 +55,7 @@ const BREAK_APPROVALS = membersOf({
55
55
  "not-requested": true, "awaiting-approval": true, granted: true, "starting-after-task": true, "on-break": true,
56
56
  });
57
57
  const TEAM_AVAILABILITIES = membersOf({
58
- ready: true, "on-task": true, "on-break": true, reserved: true, "signed-out": true,
58
+ ready: true, "on-task": true, "on-break": true, elsewhere: true, "signed-out": true,
59
59
  });
60
60
  const HISTORY_STEPS = membersOf({
61
61
  queued: true, offered: true, answered: true, held: true, muted: true, transferred: true, conferenced: true, unanswered: true,
@@ -85,7 +85,7 @@ const MEMBER_BREAKS = membersOf({
85
85
  const OFFERABLE_PHASES = membersOf({
86
86
  pending: true,
87
87
  });
88
- const PREVIEW_DEADLINES = membersOf({ calls: true, "host-calls": true, waits: true });
88
+ const PREVIEW_DEADLINES = membersOf({ "provider-dials": true, "host-dials": true, waits: true });
89
89
  const PHONES = membersOf({ softphone: true, deskPhone: true });
90
90
  const LISTENING_MODES = membersOf({ listen: true, coach: true, "join-call": true });
91
91
  const COMPLETED_BY = membersOf({ agent: true, provider: true });
@@ -923,13 +923,13 @@ function validateHistory(value, path, into, task = {}) {
923
923
  }
924
924
  else {
925
925
  // An open leg is one still running, and the task says whether it can be: a hold runs only while
926
- // the task is paused, and a mute only while its media is up. An open entry after that is a leg
926
+ // the task is paused, and a mute only while its audio is up. An open entry after that is a leg
927
927
  // nobody closed, which reads exactly like a leg running now.
928
928
  if (entry.step === "held") {
929
929
  into.require(task.phase === "paused", "task.history.held.open", `${at}.seconds`, `a held entry without seconds is a hold still running, and the task is ${describeValue(task.phase)}: the hold has ended, and its duration is stated`);
930
930
  }
931
931
  if (entry.step === "muted") {
932
- into.require(task.media !== "ended" && task.phase !== "completing", "task.history.muted.open", `${at}.seconds`, "a muted entry without seconds is still running, but this interaction's media has ended: the provider closes its open leg and states the duration; other channels may continue");
932
+ into.require(task.audio !== "ended" && task.phase !== "completing", "task.history.muted.open", `${at}.seconds`, "a muted entry without seconds is still running, but this interaction's audio has ended: the provider closes its open leg and states the duration; other channels may continue");
933
933
  }
934
934
  }
935
935
  // A muted entry carries whose the silence was, as the host reported it; no other step has it.
@@ -960,22 +960,22 @@ function validateHistory(value, path, into, task = {}) {
960
960
  }
961
961
  /** Present only while consulting, and only on voice: elsewhere there is nobody to consult. */
962
962
  const TASK_MEDIA_STATES = membersOf({ started: true, ended: true });
963
- /** Real-time media is a voice affair, and its state is one of two words. */
963
+ /** Real-time audio is a voice affair, and its state is one of two words. */
964
964
  /** A host dial, or explicitly provider-triggered preview dial, is ringing the party. */
965
965
  function partyRingingBeforeWork(task) {
966
- return Array.isArray(task.onCall) && task.onCall.some(entry => isPlainObject(entry) && entry.role === "party" && entry.stage === "ringing" && (isFilled(entry.dialId) || (task.phase === "preview" && task.atDeadline === "calls" && entry.dialId === undefined)));
966
+ return Array.isArray(task.onCall) && task.onCall.some(entry => isPlainObject(entry) && entry.role === "party" && entry.stage === "ringing" && (isFilled(entry.dialId) || (task.phase === "preview" && task.atDeadline === "provider-dials" && entry.dialId === undefined)));
967
967
  }
968
- function validateTaskMedia(task, value, channel, phase, path, into) {
968
+ function validateTaskAudio(task, value, channel, phase, path, into) {
969
969
  if (value === undefined)
970
970
  return;
971
- if (!into.require(channel === "voice", "task.media.channel", path, `a ${channel} task carries no real-time media state`))
971
+ if (!into.require(channel === "voice", "task.audio.channel", path, `a ${channel} task carries no real-time audio state`))
972
972
  return;
973
- if (into.oneOf(value, TASK_MEDIA_STATES, "task.media", path)) {
974
- // Nothing is acquired while pending, and a preview has placed no call: media names a task whose
973
+ if (into.oneOf(value, TASK_MEDIA_STATES, "task.audio", path)) {
974
+ // Nothing is acquired while pending, and a preview has placed no call: audio names a task whose
975
975
  // work has begun, on a snapshot as on the event, or a host would open the microphone on an offer.
976
976
  // Ring-back may precede answer for a host dial or an explicitly provider-triggered preview.
977
- // A deadline alone is never evidence that media started.
978
- into.require(!WORK_NOT_BEGUN.includes(phase) || partyRingingBeforeWork(task), "task.media.beforeWork", path, `a ${describeValue(phase)} task has no media: audio arrives once its work has begun, or once an evidenced host/provider preview dial is ringing its party`);
977
+ // A deadline alone is never evidence that audio started.
978
+ into.require(!WORK_NOT_BEGUN.includes(phase) || partyRingingBeforeWork(task), "task.audio.beforeWork", path, `a ${describeValue(phase)} task has no audio: audio arrives once its work has begun, or once an evidenced host/provider preview dial is ringing its party`);
979
979
  }
980
980
  }
981
981
  const WORK_NOT_BEGUN = ["pending", "confirmed", "preview"];
@@ -1131,7 +1131,7 @@ function validateTaskInto(task, context, path, into) {
1131
1131
  into.timestamp(task.previewEndsAt, "task.preview.previewEndsAt", `${path}.previewEndsAt`);
1132
1132
  }
1133
1133
  if (task.atDeadline === undefined) {
1134
- into.add("task.preview.atDeadline.required", `${path}.atDeadline`, "a preview with a deadline says what happens at it: provider calls, host calls, or waits for the agent -- an agent counting down has to know which");
1134
+ into.add("task.preview.atDeadline.required", `${path}.atDeadline`, "a preview with a deadline says what happens at it: provider dials, host dials, or waits for the agent -- an agent counting down has to know which");
1135
1135
  }
1136
1136
  else {
1137
1137
  into.oneOf(task.atDeadline, PREVIEW_DEADLINES, "task.preview.atDeadline", `${path}.atDeadline`);
@@ -1200,18 +1200,18 @@ function validateTaskInto(task, context, path, into) {
1200
1200
  if (context.member !== true || task.browsers !== undefined)
1201
1201
  validateBrowsers(task.browsers, `${path}.browsers`, into);
1202
1202
  validateTaskAttributes(task.attributes, `${path}.attributes`, into, context.levels);
1203
- validateHistory(task.history, `${path}.history`, into, { phase: task.phase, media: task.media });
1203
+ validateHistory(task.history, `${path}.history`, into, { phase: task.phase, audio: task.audio });
1204
1204
  validateOnCall(task.onCall, context.channel, `${path}.onCall`, into);
1205
- // Media and onCall describe this agent's interaction, not the continuing caller journey.
1206
- // Once the interaction's media ends or it enters wrap, its live room is cleared. Other
1205
+ // Audio and onCall describe this agent's interaction, not the continuing caller journey.
1206
+ // Once the interaction's audio ends or it enters wrap, its live room is cleared. Other
1207
1207
  // channels may remain connected under another interaction or in IVR/queue stages.
1208
- if (task.phase === "completing" && task.media === "started") {
1209
- into.add("task.media.completing", `${path}.media`, "a completing task's interaction media has ended or never started; it cannot still be started during wrap");
1208
+ if (task.phase === "completing" && task.audio === "started") {
1209
+ into.add("task.audio.completing", `${path}.audio`, "a completing task's interaction audio has ended or never started; it cannot still be started during wrap");
1210
1210
  }
1211
- if (Array.isArray(task.onCall) && task.onCall.length > 0 && (task.phase === "completing" || task.media === "ended")) {
1212
- into.add("task.onCall.ended", `${path}.onCall`, `this interaction is no longer connected (${task.phase === "completing" ? "the task is completing" : "its media ended"}); clear its onCall view without implying that other channels ended`);
1211
+ if (Array.isArray(task.onCall) && task.onCall.length > 0 && (task.phase === "completing" || task.audio === "ended")) {
1212
+ into.add("task.onCall.ended", `${path}.onCall`, `this interaction is no longer connected (${task.phase === "completing" ? "the task is completing" : "its audio ended"}); clear its onCall view without implying that other channels ended`);
1213
1213
  }
1214
- validateTaskMedia(task, task.media, context.channel, task.phase, `${path}.media`, into);
1214
+ validateTaskAudio(task, task.audio, context.channel, task.phase, `${path}.audio`, into);
1215
1215
  validateLeadAssist(task.leadAssist, context.channel, `${path}.leadAssist`, into);
1216
1216
  validateTakenOver(task.takenOver, context.channel, `${path}.takenOver`, into);
1217
1217
  // Where the terms came from is stated with them: a host cannot tell "the platform permits
@@ -1724,8 +1724,8 @@ export function validateSnapshot(snapshot, manifest, path = "snapshot", context
1724
1724
  if (snapshot.contacts === undefined && idle.contacts === true) {
1725
1725
  into.add("snapshot.contacts.required", `${path}.contacts`, "the manifest declares contacts, so every snapshot carries the contribution: [] when there are none");
1726
1726
  }
1727
- if (snapshot.scheduledActivities === undefined && idle.calendar === true) {
1728
- into.add("snapshot.calendar.required", `${path}.scheduledActivities`, "the manifest declares calendar, so every snapshot carries the contribution: [] when there are none");
1727
+ if (snapshot.calendar === undefined && idle.calendar === true) {
1728
+ into.add("snapshot.calendar.required", `${path}.calendar`, "the manifest declares calendar, so every snapshot carries the contribution: [] when there are none");
1729
1729
  }
1730
1730
  if (snapshot.contacts !== undefined) {
1731
1731
  into.require(idle.contacts === true, "snapshot.contacts.capability", `${path}.contacts`, "contacts require the contacts idle capability");
@@ -1736,22 +1736,22 @@ export function validateSnapshot(snapshot, manifest, path = "snapshot", context
1736
1736
  into.add("snapshot.contacts.shape", `${path}.contacts`, "contacts must be an array when present");
1737
1737
  }
1738
1738
  }
1739
- if (snapshot.scheduledActivities !== undefined) {
1740
- into.require(idle.calendar === true, "snapshot.calendar.capability", `${path}.scheduledActivities`, "scheduled activities require the calendar idle capability");
1741
- if (Array.isArray(snapshot.scheduledActivities)) {
1739
+ if (snapshot.calendar !== undefined) {
1740
+ into.require(idle.calendar === true, "snapshot.calendar.capability", `${path}.calendar`, "scheduled activities require the calendar idle capability");
1741
+ if (Array.isArray(snapshot.calendar)) {
1742
1742
  const seen = new Set();
1743
- snapshot.scheduledActivities.forEach((activity, index) => {
1744
- validateScheduledActivityInto(activity, `${path}.scheduledActivities[${index}]`, into, levels);
1743
+ snapshot.calendar.forEach((activity, index) => {
1744
+ validateScheduledActivityInto(activity, `${path}.calendar[${index}]`, into, levels);
1745
1745
  if (isPlainObject(activity) && isFilled(activity.id)) {
1746
1746
  if (seen.has(activity.id)) {
1747
- into.add("activity.id.unique", `${path}.scheduledActivities[${index}].id`, `duplicate activity id: ${activity.id}`);
1747
+ into.add("activity.id.unique", `${path}.calendar[${index}].id`, `duplicate activity id: ${activity.id}`);
1748
1748
  }
1749
1749
  seen.add(activity.id);
1750
1750
  }
1751
1751
  });
1752
1752
  }
1753
1753
  else {
1754
- into.add("snapshot.calendar.shape", `${path}.scheduledActivities`, "scheduledActivities must be an array when present");
1754
+ into.add("snapshot.calendar.shape", `${path}.calendar`, "calendar must be an array when present");
1755
1755
  }
1756
1756
  }
1757
1757
  // The login is the permission: a lead's snapshot carries a team member list, `members: []` included,
@@ -1903,13 +1903,13 @@ export function validateEventEnvelope(envelope, manifest, path = "event", contex
1903
1903
  case "task-updated":
1904
1904
  validateTaskInto(event.task, { channel, levels, autoAcceptTasks: context.autoAcceptTasks, dialOutcomesDeclared: manifestDials(manifest), locked: context.locked }, `${at}.task`, into);
1905
1905
  break;
1906
- case "task-media-started":
1907
- into.require(channel === "voice", "event.media.channel", `${at}.type`, "only a voice provider publishes media transitions");
1908
- into.require(isAssignmentId(event.assignmentId), "event.taskMediaStarted.assignmentId", `${at}.assignmentId`, "an event about a task names its assignment");
1906
+ case "task-audio-started":
1907
+ into.require(channel === "voice", "event.audio.channel", `${at}.type`, "only a voice provider publishes audio transitions");
1908
+ into.require(isAssignmentId(event.assignmentId), "event.taskAudioStarted.assignmentId", `${at}.assignmentId`, "an event about a task names its assignment");
1909
1909
  break;
1910
- case "task-media-ended":
1911
- into.require(channel === "voice", "event.media.channel", `${at}.type`, "only a voice provider publishes media transitions");
1912
- into.require(isAssignmentId(event.assignmentId), "event.taskMediaEnded.assignmentId", `${at}.assignmentId`, "an event about a task names its assignment");
1910
+ case "task-audio-ended":
1911
+ into.require(channel === "voice", "event.audio.channel", `${at}.type`, "only a voice provider publishes audio transitions");
1912
+ into.require(isAssignmentId(event.assignmentId), "event.taskAudioEnded.assignmentId", `${at}.assignmentId`, "an event about a task names its assignment");
1913
1913
  break;
1914
1914
  case "task-ended":
1915
1915
  into.require(isAssignmentId(event.assignmentId), "event.taskEnded.assignmentId", `${at}.assignmentId`, "an event about a task names its assignment");
@@ -1953,13 +1953,13 @@ export function validateEventEnvelope(envelope, manifest, path = "event", contex
1953
1953
  into.require(context.leadFeatures !== false, "event.team.features", `${at}.team`, "the lead turned the team feature off, so nothing of the team reaches them");
1954
1954
  validateTeamMembersInto(event.team, `${at}.team`, { ...context, levels, channel: isChannel(channel) ? channel : undefined }, into);
1955
1955
  break;
1956
- case "team-media-started":
1957
- case "team-media-ended":
1956
+ case "team-audio-started":
1957
+ case "team-audio-ended":
1958
1958
  // The lead's channel in a member's call: audio the application opens on the member's assignment.
1959
- into.require(channel === "voice", "event.media.channel", `${at}.type`, "only a voice provider publishes media transitions");
1960
- into.require(context.capabilities === undefined || context.capabilities.lead === true, "event.teamMedia.capability", `${at}.type`, "team media reaches a login that declares lead, and nobody else");
1961
- into.require(isUserId(event.memberId), "event.teamMedia.memberId", `${at}.memberId`, "team media names the member whose call the lead is on");
1962
- into.require(isAssignmentId(event.assignmentId), "event.teamMedia.assignmentId", `${at}.assignmentId`, "team media names the member's assignment");
1959
+ into.require(channel === "voice", "event.audio.channel", `${at}.type`, "only a voice provider publishes audio transitions");
1960
+ into.require(context.capabilities === undefined || context.capabilities.lead === true, "event.teamAudio.capability", `${at}.type`, "team audio reaches a login that declares lead, and nobody else");
1961
+ into.require(isUserId(event.memberId), "event.teamAudio.memberId", `${at}.memberId`, "team audio names the member whose call the lead is on");
1962
+ into.require(isAssignmentId(event.assignmentId), "event.teamAudio.assignmentId", `${at}.assignmentId`, "team audio names the member's assignment");
1963
1963
  break;
1964
1964
  case "contacts-updated":
1965
1965
  into.require(idle.contacts === true, "event.contacts.capability", `${at}.contacts`, "contacts-updated requires the contacts idle capability");
@@ -1971,17 +1971,17 @@ export function validateEventEnvelope(envelope, manifest, path = "event", contex
1971
1971
  }
1972
1972
  break;
1973
1973
  case "calendar-updated":
1974
- into.require(idle.calendar === true, "event.calendar.capability", `${at}.scheduledActivities`, "calendar-updated requires the calendar idle capability");
1975
- if (!Array.isArray(event.scheduledActivities)) {
1976
- into.add("event.calendar.shape", `${at}.scheduledActivities`, "scheduledActivities must be an array");
1974
+ into.require(idle.calendar === true, "event.calendar.capability", `${at}.calendar`, "calendar-updated requires the calendar idle capability");
1975
+ if (!Array.isArray(event.calendar)) {
1976
+ into.add("event.calendar.shape", `${at}.calendar`, "calendar must be an array");
1977
1977
  }
1978
1978
  else {
1979
1979
  const ids = new Set();
1980
- event.scheduledActivities.forEach((activity, index) => {
1981
- validateScheduledActivityInto(activity, `${at}.scheduledActivities[${index}]`, into, levels);
1980
+ event.calendar.forEach((activity, index) => {
1981
+ validateScheduledActivityInto(activity, `${at}.calendar[${index}]`, into, levels);
1982
1982
  if (isPlainObject(activity) && isFilled(activity.id)) {
1983
1983
  if (ids.has(activity.id))
1984
- into.add("activity.id.unique", `${at}.scheduledActivities[${index}].id`, `duplicate activity id: ${activity.id}`);
1984
+ into.add("activity.id.unique", `${at}.calendar[${index}].id`, `duplicate activity id: ${activity.id}`);
1985
1985
  ids.add(activity.id);
1986
1986
  }
1987
1987
  });
@@ -2293,7 +2293,7 @@ export function validateTaskCommand(command, task, path = "command", taskContext
2293
2293
  if (type !== "custom" && type !== "recording") {
2294
2294
  let fields = ["type"];
2295
2295
  switch (type) {
2296
- case "call":
2296
+ case "dial":
2297
2297
  case "connect-back":
2298
2298
  fields.push("dialId");
2299
2299
  break;
@@ -2320,8 +2320,8 @@ export function validateTaskCommand(command, task, path = "command", taskContext
2320
2320
  }
2321
2321
  const dial = (rule) => into.filled(command.dialId, rule, `${path}.dialId`, "a command that dials carries the host's dialId");
2322
2322
  switch (type) {
2323
- case "call":
2324
- dial("command.call.dialId");
2323
+ case "dial":
2324
+ dial("command.dial.dialId");
2325
2325
  break;
2326
2326
  case "connect-back":
2327
2327
  dial("command.connectBack.dialId");
@@ -2433,7 +2433,7 @@ export function validateTaskCommand(command, task, path = "command", taskContext
2433
2433
  if (type === "decline")
2434
2434
  offered("decline");
2435
2435
  break;
2436
- case "call":
2436
+ case "dial":
2437
2437
  inPhase("command.phase.preview", "preview");
2438
2438
  break;
2439
2439
  case "hold":
@@ -2458,7 +2458,7 @@ export function validateTaskCommand(command, task, path = "command", taskContext
2458
2458
  into.violations.push(...validateRecordingCommandState(command, policy, state, path));
2459
2459
  if (command.action === "start" || command.action === "resume") {
2460
2460
  interaction();
2461
- into.require(task.media === "started", "recording.request.phase", path, "capture needs live media");
2461
+ into.require(task.audio === "started", "recording.request.phase", path, "capture needs live audio");
2462
2462
  }
2463
2463
  else
2464
2464
  inPhase("command.phase.interaction", "in-progress", "paused", "completing");
@@ -2551,7 +2551,7 @@ const RESULT_STATUSES = {
2551
2551
  cancelBreak: { success: "cancelled", failure: "failed" },
2552
2552
  endBreak: { success: "ended", failure: "failed" },
2553
2553
  executeTeam: { success: "applied", failure: "failed" },
2554
- openMedia: { success: "opened", failure: "unavailable" },
2554
+ openAudio: { success: "opened", failure: "unavailable" },
2555
2555
  setPreference: { success: "applied", failure: "failed" },
2556
2556
  recordStep: { success: "recorded", failure: "failed" },
2557
2557
  };
@@ -2571,22 +2571,22 @@ export function validateResult(result, method, path = "result", dialId) {
2571
2571
  into.add("result.shape", path, `${method} must answer an object`);
2572
2572
  return into.violations;
2573
2573
  }
2574
- if (method === "openMedia" && result.status !== "opened") {
2575
- into.require(result.session === undefined, "result.session.unexpected", `${path}.session`, "only opened carries a media session");
2574
+ if (method === "openAudio" && result.status !== "opened") {
2575
+ into.require(result.audio === undefined, "result.audio.unexpected", `${path}.audio`, "only opened carries an audio session");
2576
2576
  }
2577
2577
  if (result.status === statuses.success) {
2578
2578
  into.require(result.failure === undefined, "result.failure.unexpected", `${path}.failure`, `${statuses.success} carries no failure`);
2579
- if (method === "openMedia") {
2580
- if (isPlainObject(result.session)) {
2579
+ if (method === "openAudio") {
2580
+ if (isPlainObject(result.audio)) {
2581
2581
  // Streams may come from another realm, and Node conformance runs have no MediaStream
2582
2582
  // constructor. Check the boundary shape without claiming to verify live audio.
2583
- into.require(isPlainObject(result.session.remoteAudio), "result.session.remoteAudio", `${path}.session.remoteAudio`, "an opened session carries its remote audio stream");
2583
+ into.require(isPlainObject(result.audio.remoteAudio), "result.audio.remoteAudio", `${path}.audio.remoteAudio`, "an opened session carries its remote audio stream");
2584
2584
  for (const method of ["setMuted", "close"]) {
2585
- into.require(typeof result.session[method] === "function", `result.session.${method}`, `${path}.session.${method}`, `a media session implements ${method}`);
2585
+ into.require(typeof result.audio[method] === "function", `result.audio.${method}`, `${path}.audio.${method}`, `an audio session implements ${method}`);
2586
2586
  }
2587
2587
  }
2588
2588
  else {
2589
- into.add("result.session", `${path}.session`, "opened carries the media session");
2589
+ into.add("result.audio", `${path}.audio`, "opened carries the audio session");
2590
2590
  }
2591
2591
  }
2592
2592
  if (dials) {
@@ -2893,7 +2893,7 @@ export function validateRecordingPolicy(value, path = "capabilities.recording")
2893
2893
  check(false, "policy.shape", at, "expected permitted actions");
2894
2894
  continue;
2895
2895
  }
2896
- keys(policy, [...RECORDING_ACTIONS, ...(source === "host" ? ["destinationId"] : [])], at);
2896
+ keys(policy, [...RECORDING_ACTIONS, ...(source === "host" ? ["storageId"] : [])], at);
2897
2897
  check(RECORDING_ACTIONS.some(action => policy[action] !== undefined), "policy.empty", at, "omit paths offering no actions");
2898
2898
  for (const action of RECORDING_ACTIONS) {
2899
2899
  if (policy[action] === undefined)
@@ -2901,7 +2901,7 @@ export function validateRecordingPolicy(value, path = "capabilities.recording")
2901
2901
  check(policy[action] === true, "policy.action", `${at}.${action}`, "permission is true or absent");
2902
2902
  }
2903
2903
  if (source === "host")
2904
- check(filled(policy.destinationId), "destination", `${at}.destinationId`, "host storage destination must be explicit per task");
2904
+ check(filled(policy.storageId), "storage", `${at}.storageId`, "host storage must be named per task");
2905
2905
  }
2906
2906
  return violations;
2907
2907
  }
@@ -2909,15 +2909,15 @@ export function validateHostRecording(value, softphone, path = "host.recording")
2909
2909
  if (value === undefined)
2910
2910
  return [];
2911
2911
  const { violations, check, keys } = collector();
2912
- check(softphone, "host.channel", path, "initial host recording requires voice softphone media");
2912
+ check(softphone, "host.channel", path, "initial host recording requires voice softphone audio");
2913
2913
  if (!object(value)) {
2914
2914
  check(false, "host.shape", path, "expected host recording declaration and executor");
2915
2915
  return violations;
2916
2916
  }
2917
- keys(value, ["actions", "destinationIds", "execute", "announcesToCaller"], path);
2917
+ keys(value, ["actions", "storageIds", "execute", "announcesToCaller"], path);
2918
2918
  if (value.announcesToCaller !== undefined)
2919
2919
  check(value.announcesToCaller === true, "host.announcesToCaller", `${path}.announcesToCaller`, "caller announcement guarantee is true or absent");
2920
- for (const [key, allowed] of [["actions", RECORDING_ACTIONS], ["destinationIds", undefined]]) {
2920
+ for (const [key, allowed] of [["actions", RECORDING_ACTIONS], ["storageIds", undefined]]) {
2921
2921
  const list = value[key];
2922
2922
  if (!Array.isArray(list)) {
2923
2923
  check(false, "host.list", `${path}.${key}`, "expected explicit array");
@@ -3033,14 +3033,14 @@ export function validateRecordingRequest(request, task, context, path = "request
3033
3033
  state = Array.isArray(reports) ? reports.find(r => object(r) && r.assignmentId === task.assignmentId)?.state : undefined;
3034
3034
  if (object(context.host)) {
3035
3035
  check(Array.isArray(context.host.actions) && context.host.actions.includes(command.action), "host.action", path, "host does not support action");
3036
- check(object(policy) && Array.isArray(context.host.destinationIds) && context.host.destinationIds.includes(policy.destinationId), "host.destination", path, "task destination is not provisioned by this host");
3036
+ check(object(policy) && Array.isArray(context.host.storageIds) && context.host.storageIds.includes(policy.storageId), "host.storage", path, "task storage is not one this host provisions");
3037
3037
  }
3038
3038
  }
3039
3039
  const fresh = effectiveRecordingState(state, context.now);
3040
3040
  check(fresh.status !== "unknown", "request.freshness", path, "current state or trusted observer time unavailable/expired");
3041
3041
  violations.push(...validateRecordingCommandState(command, policy, fresh, `${path}.command`));
3042
3042
  const opening = command.action === "start" || command.action === "resume";
3043
- check(opening ? ["in-progress", "paused"].includes(task.phase) && task.media === "started" : ["in-progress", "paused", "completing"].includes(task.phase), "request.phase", path, "capture needs live task media; terminal controls may finish recordings during wrap-up");
3043
+ check(opening ? ["in-progress", "paused"].includes(task.phase) && task.audio === "started" : ["in-progress", "paused", "completing"].includes(task.phase), "request.phase", path, "capture needs live task audio; terminal controls may finish recordings during wrap-up");
3044
3044
  return violations;
3045
3045
  }
3046
3046
  /** Check recording-specific result semantics against the owner's confirming observations. */