@xema/omni-protocol 0.1.50 → 0.1.52

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.
@@ -956,6 +956,12 @@ function validateTaskInto(task, context, path, into) {
956
956
  validateTaskAttributes(task.attributes, `${path}.attributes`, into);
957
957
  validateHandlingHistory(task.handlingHistory, `${path}.handlingHistory`, into);
958
958
  validateOnCall(task.onCall, context.channel, `${path}.onCall`, into);
959
+ // The room is who is on the call now, and a task outlives its call by the whole of wrap-up: a
960
+ // task whose call has ended -- completing, or media ended -- carries nobody, or the last thing the
961
+ // wire said about the call stays true for ever. Empty and absent both say nobody.
962
+ if (Array.isArray(task.onCall) && task.onCall.length > 0 && (task.phase === "completing" || task.media === "ended")) {
963
+ into.add("task.onCall.ended", `${path}.onCall`, `the call has ended (${task.phase === "completing" ? "the task is completing" : "its media ended"}) and onCall still names people on it: the room is who is on the call now, and now nobody is`);
964
+ }
959
965
  validateTaskMedia(task.media, context.channel, `${path}.media`, into);
960
966
  validateLeadAssist(task.leadAssist, context.channel, `${path}.leadAssist`, into);
961
967
  validateAssisting(task.assisting, context.channel, `${path}.assisting`, into);
@@ -1796,6 +1802,16 @@ export function validateTaskCommand(command, task, path = "command") {
1796
1802
  }
1797
1803
  if (!isPlainObject(task))
1798
1804
  return into.violations;
1805
+ // The task the command is held to has to be one the wire published. A host's own mapping of it,
1806
+ // handed in for want of the original, is not, and checking a command against it would be
1807
+ // checking against a task nobody has -- so the task is validated first, and a command is held
1808
+ // only to a task that stands.
1809
+ const published = new Collector();
1810
+ validateTaskInto(task, { channel: channel ?? "voice" }, `${path}.task`, published);
1811
+ if (published.violations.length > 0) {
1812
+ into.add("command.task", `${path}.task`, `the task the command is held to is not one the wire published (${published.violations.map(v => v.rule).join(", ")}): pass the task as the provider sent it, or validate the command's shape alone`);
1813
+ return into.violations;
1814
+ }
1799
1815
  // What the task has to offer or be in for the command to be issuable.
1800
1816
  const capabilities = isPlainObject(task.capabilities) ? task.capabilities : {};
1801
1817
  const offered = (name) => {
package/guide.md CHANGED
@@ -2994,6 +2994,26 @@ the platform added itself carries none. `held: true` is presence as claim. A sna
2994
2994
  after a transfer reads the room from here rather than inferring it from a sequence of outcomes it
2995
2995
  never saw.
2996
2996
 
2997
+ **`onCall` is who is on the call now, and a task outlives its call.** Wrap-up is not an ending:
2998
+ the task is alive, the agent is working, and nothing arrives to say the call ended until they
2999
+ dispose. So the room must not be the last word about a call that has ended. Once the call is over
3000
+ -- the task `completing`, or its `media` ended -- `onCall` is empty, or absent only where the
3001
+ provider never publishes the room at all, and a task carrying people on a call that has ended is
3002
+ refused (`task.onCall.ended`). Empty says nobody is on the call; absent says the provider does not
3003
+ say who is, which is a different claim, so a provider that publishes the room publishes one last
3004
+ change: the empty room.
3005
+
3006
+ **A field that describes the present is cleared by the transition that ends it.** `onCall`,
3007
+ `previewEndsAt` and `atDeadline` are three instances of one shape, and there will be more: each
3008
+ describes a state that is true now, and each is carried past the moment it stops being true by the
3009
+ most natural implementation there is, building the next task by spreading the last one. The
3010
+ provider's own state looks right, the claim on the wire is stale, and only the host sees it. An
3011
+ adapter that builds each transition from what is true after it, rather than from what was true
3012
+ before, needs none of these rules named. Two agents were
3013
+ once shown on calls for the better part of an hour while callers queued, because the last message
3014
+ describing the room live was never followed by one saying it had emptied, and every consumer that
3015
+ derived anything from it was wrong in a way that looked like its own bug.
3016
+
2997
3017
  **A dialled entry is listed from the moment the dial is placed, not from the answer.** The entry is
2998
3018
  what makes `transfer` `cancel` issuable and what `conference` `remove` names, and a destination the
2999
3019
  agent cannot call off while it rings is a parked customer with no way back -- which is the very case
@@ -3887,7 +3907,13 @@ declared:
3887
3907
  | Everything else | Its own named capability. |
3888
3908
 
3889
3909
  `validateTaskCommand(command, task)` holds a command to this table at runtime, both ways: the
3890
- capability it needs, the phase it belongs to, and the state that has to stand.
3910
+ capability it needs, the phase it belongs to, and the state that has to stand. The task it wants
3911
+ is the one the provider published, not a host's own mapping of it: a host that keeps only its
3912
+ mapped shape has nothing honest to pass, and then checks shape alone, which is still worth doing
3913
+ -- it names a command the wire never had -- but is not the table. Keeping the published task
3914
+ beside the mapped one is what the full check costs a host; an adapter has it for free. The
3915
+ validator holds a command only to a task that stands: a task handed in that is not one the wire
3916
+ published is named (`command.task`) rather than checked against.
3891
3917
 
3892
3918
  Declining or rejecting a pending offer ends it without accepting or completing it. The provider
3893
3919
  confirms the end with `task-ended` and a `cancelled` outcome.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xema/omni-protocol",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "description": "The Omni protocol: the contract every provider adapter implements",
5
5
  "type": "module",
6
6
  "license": "MIT",