@xema/omni-protocol 0.1.48 → 0.1.49
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 +4 -14
- package/dist/validation.d.ts +2 -0
- package/dist/validation.js +7 -0
- package/guide.md +3 -4
- package/package.json +1 -1
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { browserSessionKey, effectiveLevels, sameCapabilities, } from "./index.js";
|
|
2
|
-
import { assertNoViolations, validateAuthenticationState, validateEventEnvelope, validateHostGuarantees, validateHostReport, validateManifest, validateResult, validateSnapshot, validateTimeZone, validatePhone, isTimeZone, } from "./validation.js";
|
|
2
|
+
import { assertNoViolations, validateAuthenticationState, validateEventEnvelope, validateHostGuarantees, validateHostReport, validateManifest, validateResult, validateSnapshot, validateTimeZone, validatePhone, 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
|
|
@@ -257,18 +257,6 @@ export async function exerciseAdapter(adapter, context, options = {}) {
|
|
|
257
257
|
violations.push(...validateHostGuarantees(context.host.guarantees, "context.host.guarantees"));
|
|
258
258
|
// The zone is the host's to state, so a context without one is a host that cannot exist.
|
|
259
259
|
violations.push(...validateTimeZone(context.timeZone, "context.timeZone"));
|
|
260
|
-
// A provider that reads its own clock passes the round trip on any machine in the host's zone,
|
|
261
|
-
// which for a demo and a runner in one datacentre is most of them. A conformance run states a
|
|
262
|
-
// zone this machine is not in, so the only way the identity carries it is that the provider
|
|
263
|
-
// took it from the host.
|
|
264
|
-
if (isTimeZone(context.timeZone)) {
|
|
265
|
-
const canonical = (zone) => new Intl.DateTimeFormat("en", { timeZone: zone }).resolvedOptions().timeZone;
|
|
266
|
-
const local = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
267
|
-
if (canonical(context.timeZone) === canonical(local)) {
|
|
268
|
-
violations.push({ rule: "context.timeZone.local", path: "context.timeZone",
|
|
269
|
-
message: `a conformance run states a zone this machine is not in; ${context.timeZone} is this machine's, so a provider reading its own clock would pass the round trip` });
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
260
|
// How the agent hears the call decides what the host owns: on a softphone the host has the
|
|
273
261
|
// audio and reports it; on a desk phone, or off voice, there is none for it to report.
|
|
274
262
|
violations.push(...validatePhone(context.phone, adapter.manifest, "context.phone"));
|
|
@@ -354,7 +342,9 @@ export async function exerciseAdapter(adapter, context, options = {}) {
|
|
|
354
342
|
// The provider republishes the agent's day: once connected, the identity carries the zone the
|
|
355
343
|
// host sent. That proves the round trip, not the store -- an echo passes it -- and the name
|
|
356
344
|
// says only what it tests; storage is proved by a colleague's zone arriving from describeUsers().
|
|
357
|
-
|
|
345
|
+
// A zone is judged by what it denotes: Asia/Kolkata and Asia/Calcutta are one zone, and a
|
|
346
|
+
// provider that keeps the canonical name has kept the zone.
|
|
347
|
+
if (isTimeZone(context.timeZone) && !sameTimeZone(current().identity.timeZone, context.timeZone)) {
|
|
358
348
|
violations.push({
|
|
359
349
|
rule: "authentication.identity.timeZone.republished",
|
|
360
350
|
path: "authentication.identity.timeZone",
|
package/dist/validation.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export type ResultMethod = "execute" | "dial" | "setCapacity" | "requestBreak" |
|
|
|
74
74
|
export declare function validateResult(result: unknown, method: ResultMethod, path?: string, dialId?: string): ProtocolViolation[];
|
|
75
75
|
/** An IANA zone name the runtime knows: `Asia/Kolkata` is one, an offset or a made-up name is not. */
|
|
76
76
|
export declare function isTimeZone(value: unknown): value is string;
|
|
77
|
+
/** Whether two zone names denote one zone: `Asia/Kolkata` and `Asia/Calcutta` do, and a name is judged by what it denotes. */
|
|
78
|
+
export declare function sameTimeZone(a: unknown, b: unknown): boolean;
|
|
77
79
|
/** The zone a host sends at connect: required, and an IANA name. */
|
|
78
80
|
export declare function validateTimeZone(value: unknown, path?: string): ProtocolViolation[];
|
|
79
81
|
/** What a login is validated against beyond its own shape. */
|
package/dist/validation.js
CHANGED
|
@@ -1788,6 +1788,13 @@ export function isTimeZone(value) {
|
|
|
1788
1788
|
return false;
|
|
1789
1789
|
}
|
|
1790
1790
|
}
|
|
1791
|
+
/** Whether two zone names denote one zone: `Asia/Kolkata` and `Asia/Calcutta` do, and a name is judged by what it denotes. */
|
|
1792
|
+
export function sameTimeZone(a, b) {
|
|
1793
|
+
if (!isTimeZone(a) || !isTimeZone(b))
|
|
1794
|
+
return false;
|
|
1795
|
+
const canonical = (zone) => new Intl.DateTimeFormat("en", { timeZone: zone }).resolvedOptions().timeZone;
|
|
1796
|
+
return a === b || canonical(a) === canonical(b);
|
|
1797
|
+
}
|
|
1791
1798
|
/** The zone a host sends at connect: required, and an IANA name. */
|
|
1792
1799
|
export function validateTimeZone(value, path = "context.timeZone") {
|
|
1793
1800
|
const into = new Collector();
|
package/guide.md
CHANGED
|
@@ -1939,10 +1939,9 @@ field to fill in later: an identity without one is refused (`authentication.iden
|
|
|
1939
1939
|
so is a zone that is not an IANA name, wherever it appears. Nothing is ever assumed in its place --
|
|
1940
1940
|
not the viewer's browser, which gives a different answer per reader for the same record, and not the
|
|
1941
1941
|
provider's clock. The harness holds a provider to the round trip: the identity carries the zone the
|
|
1942
|
-
host stated (`authentication.identity.timeZone.republished`),
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
in one datacentre is most of them. **The round trip is not the store.**
|
|
1942
|
+
host stated (`authentication.identity.timeZone.republished`), judged by what the name denotes:
|
|
1943
|
+
`Asia/Kolkata` and `Asia/Calcutta` are one zone, and a provider that keeps the canonical name has
|
|
1944
|
+
kept the zone. **The round trip is not the store.**
|
|
1946
1945
|
An adapter that echoes the stated zone back onto the identity passes that check with nothing kept,
|
|
1947
1946
|
and a lead reading a colleague's day would still get the wrong one. What proves the store is a zone
|
|
1948
1947
|
the run never sent: a colleague's `User` from `describeUsers()` carrying theirs. A provider's own
|