@xema/omni-protocol 0.1.95 → 0.1.96
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/testing.js +9 -9
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +5 -5
- package/guide.md +369 -364
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1687,7 +1687,7 @@ export interface Connection<C extends Channel = Channel> {
|
|
|
1687
1687
|
execute(request: TaskCommandRequest<C>): Promise<TaskCommandResult>;
|
|
1688
1688
|
disconnect(): Promise<void>;
|
|
1689
1689
|
/** Required of any adapter publishing a `UserId` -- on a forced break, a team member list, or history. */
|
|
1690
|
-
|
|
1690
|
+
getUserDetails?(ids: UserId[]): Promise<User[]>;
|
|
1691
1691
|
/** Required when the manifest declares `idleCapabilities.dial`. */
|
|
1692
1692
|
dial?(request: DialRequest): Promise<DialResult>;
|
|
1693
1693
|
/**
|
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,
|
|
2
|
+
import { assertNoViolations, validateAuthenticationState, validateBreakTransition, validateEventEnvelope, validateHostGuarantees, validateHostReport, validateUserDetails, observeRules, ruleEvaluated, validateHostMute, validateHostRecording, validateLoginStore, validateInteractionReport, 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
|
|
@@ -356,7 +356,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
|
|
|
356
356
|
violations.push(...undeterminedTasks(eventTasks(envelope), "event"));
|
|
357
357
|
lockedPartySeen ||= eventTasks(envelope).some(locksParty);
|
|
358
358
|
if (eventNamesUsers(envelope))
|
|
359
|
-
requireMethod(live, "
|
|
359
|
+
requireMethod(live, "getUserDetails", "an event publishes a UserId");
|
|
360
360
|
// Work is pulled, never pushed: an offer before the host stated capacity is an allocation
|
|
361
361
|
// against nothing, and an offer beyond the count is one too many -- unless the task is the
|
|
362
362
|
// host's own dial arriving, which counts against nothing.
|
|
@@ -458,22 +458,22 @@ export async function exerciseAdapter(adapter, context, options = {}) {
|
|
|
458
458
|
violations.push(...undeterminedTasks(tasks, `${path}.tasks`));
|
|
459
459
|
lockedPartySeen ||= tasks.some(locksParty);
|
|
460
460
|
if (publishesUserIds(read)) {
|
|
461
|
-
requireMethod(source, "
|
|
461
|
+
requireMethod(source, "getUserDetails", "the snapshot publishes a UserId");
|
|
462
462
|
// Required by presence is not enough: the names the snapshot published are looked up, and what
|
|
463
463
|
// comes back is held to the shape -- an empty answer to a team member list of colleagues is named.
|
|
464
464
|
const named = userIdsIn(read);
|
|
465
|
-
if (typeof source.
|
|
465
|
+
if (typeof source.getUserDetails === "function" && named.length > 0) {
|
|
466
466
|
let described;
|
|
467
467
|
try {
|
|
468
|
-
described = await source.
|
|
469
|
-
violations.push(...
|
|
468
|
+
described = await source.getUserDetails(named);
|
|
469
|
+
violations.push(...validateUserDetails(described, named, "connection.getUserDetails"));
|
|
470
470
|
if (Array.isArray(described) && described.length === 0) {
|
|
471
|
-
violations.push({ rule: "connection.
|
|
471
|
+
violations.push({ rule: "connection.getUserDetails.empty", path: "connection.getUserDetails",
|
|
472
472
|
message: `asked about ${named.join(", ")}, whom the snapshot itself named, the provider described nobody` });
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
catch (error) {
|
|
476
|
-
violations.push({ rule: "connection.
|
|
476
|
+
violations.push({ rule: "connection.getUserDetails.rejected", path: "connection.getUserDetails", message: `getUserDetails rejected rather than answered: ${String(error)}` });
|
|
477
477
|
}
|
|
478
478
|
}
|
|
479
479
|
}
|
|
@@ -532,7 +532,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
|
|
|
532
532
|
violations.push(...malformed);
|
|
533
533
|
// The provider republishes the agent's day: once connected, the identity carries the zone the
|
|
534
534
|
// host sent. That proves the round trip, not the store -- an echo passes it -- and the name
|
|
535
|
-
// says only what it tests; storage is proved by a colleague's zone arriving from
|
|
535
|
+
// says only what it tests; storage is proved by a colleague's zone arriving from getUserDetails().
|
|
536
536
|
// A zone is judged by what it denotes: Asia/Kolkata and Asia/Calcutta are one zone, and a
|
|
537
537
|
// provider that keeps the canonical name has kept the zone.
|
|
538
538
|
if (isTimeZone(context.timeZone) && !sameTimeZone(current().identity.timeZone, context.timeZone)) {
|
package/dist/validation.d.ts
CHANGED
|
@@ -150,11 +150,11 @@ export declare function sameTimeZone(a: unknown, b: unknown): boolean;
|
|
|
150
150
|
/** The zone a host sends at connect: required, and an IANA name. */
|
|
151
151
|
export declare function validateTimeZone(value: unknown, path?: string): ProtocolViolation[];
|
|
152
152
|
/**
|
|
153
|
-
* What `
|
|
153
|
+
* What `getUserDetails(ids)` answered: an array of users, each one asked for, each a valid identity.
|
|
154
154
|
* The provider may answer fewer than asked, which is what "unknown to it" looks like; it never
|
|
155
155
|
* answers somebody nobody asked about.
|
|
156
156
|
*/
|
|
157
|
-
export declare function
|
|
157
|
+
export declare function validateUserDetails(users: unknown, asked: readonly string[], path?: string): ProtocolViolation[];
|
|
158
158
|
/** What a login is validated against beyond its own shape. */
|
|
159
159
|
export interface LoginValidationContext {
|
|
160
160
|
/** The level ids in force, from the manifest. The defaults when absent. */
|
package/dist/validation.js
CHANGED
|
@@ -2649,21 +2649,21 @@ export function validateTimeZone(value, path = "context.timeZone") {
|
|
|
2649
2649
|
return into.violations;
|
|
2650
2650
|
}
|
|
2651
2651
|
/**
|
|
2652
|
-
* What `
|
|
2652
|
+
* What `getUserDetails(ids)` answered: an array of users, each one asked for, each a valid identity.
|
|
2653
2653
|
* The provider may answer fewer than asked, which is what "unknown to it" looks like; it never
|
|
2654
2654
|
* answers somebody nobody asked about.
|
|
2655
2655
|
*/
|
|
2656
|
-
export function
|
|
2656
|
+
export function validateUserDetails(users, asked, path = "getUserDetails") {
|
|
2657
2657
|
const into = new Collector();
|
|
2658
2658
|
if (!Array.isArray(users)) {
|
|
2659
|
-
into.add("
|
|
2659
|
+
into.add("getUserDetails.shape", path, "getUserDetails answers an array of users, empty when it knows none of them");
|
|
2660
2660
|
return into.violations;
|
|
2661
2661
|
}
|
|
2662
2662
|
users.forEach((user, index) => {
|
|
2663
2663
|
const at = `${path}[${index}]`;
|
|
2664
|
-
validateUser(user, "
|
|
2664
|
+
validateUser(user, "getUserDetails.user", at, into);
|
|
2665
2665
|
if (isPlainObject(user) && typeof user.id === "string") {
|
|
2666
|
-
into.require(asked.includes(user.id), "
|
|
2666
|
+
into.require(asked.includes(user.id), "getUserDetails.unasked", `${at}.id`, `${user.id} was not among the ids asked for`);
|
|
2667
2667
|
}
|
|
2668
2668
|
});
|
|
2669
2669
|
return into.violations;
|