@xema/omni-protocol 0.1.20 → 0.1.22
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 +6 -6
- package/dist/index.js +4 -7
- package/dist/testing.js +1 -1
- package/dist/validation.js +24 -14
- package/guide.md +15 -11
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -111,8 +111,8 @@ export interface Manifest<C extends Channel = Channel> {
|
|
|
111
111
|
phaseLabels?: TaskPhaseLabels;
|
|
112
112
|
/** Keyed by `taskType`. An entry replaces the channel default outright rather than merging. */
|
|
113
113
|
taskTypePresentation?: Record<string, TaskTypePresentation>;
|
|
114
|
-
/** The
|
|
115
|
-
|
|
114
|
+
/** The organisation's whole ladder, stated outright, `person` included. Omitted for the typical four, `DEFAULT_TIERS`. */
|
|
115
|
+
orgTiers?: TierDeclaration[];
|
|
116
116
|
}
|
|
117
117
|
export interface SecretStore {
|
|
118
118
|
get(key: string): Promise<string | undefined>;
|
|
@@ -530,7 +530,7 @@ export interface TaskAssisting {
|
|
|
530
530
|
}
|
|
531
531
|
/**
|
|
532
532
|
* A tier of the organisation's structure, by the id its manifest declares -- or one of the four
|
|
533
|
-
*
|
|
533
|
+
* a typical organisation has, `DEFAULT_TIERS`, when the manifest declares no ladder. The protocol
|
|
534
534
|
* never describes the chain: which tiers a person passes through is the structure's to know.
|
|
535
535
|
*/
|
|
536
536
|
export type Tier = string;
|
|
@@ -540,8 +540,8 @@ export interface TierDeclaration {
|
|
|
540
540
|
label: string;
|
|
541
541
|
}
|
|
542
542
|
/**
|
|
543
|
-
* The tiers a typical organisation has
|
|
544
|
-
*
|
|
543
|
+
* The tiers a typical organisation has: the ladder in force when a manifest declares no
|
|
544
|
+
* `orgTiers`. A manifest that declares any states its whole ladder outright.
|
|
545
545
|
*/
|
|
546
546
|
export declare const DEFAULT_TIERS: readonly [{
|
|
547
547
|
readonly id: "org";
|
|
@@ -556,7 +556,7 @@ export declare const DEFAULT_TIERS: readonly [{
|
|
|
556
556
|
readonly id: "person";
|
|
557
557
|
readonly label: "You";
|
|
558
558
|
}];
|
|
559
|
-
/** The
|
|
559
|
+
/** The ladder in force for a manifest: exactly what it declares, or the defaults when it declares none. */
|
|
560
560
|
export declare function effectiveTiers(declared: readonly TierDeclaration[] | undefined): TierDeclaration[];
|
|
561
561
|
/**
|
|
562
562
|
* Something the queue could allow, locked above the person: the tier that made it unchangeable,
|
package/dist/index.js
CHANGED
|
@@ -51,8 +51,8 @@ export function isAllowedBrowserUrl(url) {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
* The tiers a typical organisation has
|
|
55
|
-
*
|
|
54
|
+
* The tiers a typical organisation has: the ladder in force when a manifest declares no
|
|
55
|
+
* `orgTiers`. A manifest that declares any states its whole ladder outright.
|
|
56
56
|
*/
|
|
57
57
|
export const DEFAULT_TIERS = [
|
|
58
58
|
{ id: "org", label: "Your organisation" },
|
|
@@ -60,12 +60,9 @@ export const DEFAULT_TIERS = [
|
|
|
60
60
|
{ id: "team", label: "Your team" },
|
|
61
61
|
{ id: "person", label: "You" },
|
|
62
62
|
];
|
|
63
|
-
/** The
|
|
63
|
+
/** The ladder in force for a manifest: exactly what it declares, or the defaults when it declares none. */
|
|
64
64
|
export function effectiveTiers(declared) {
|
|
65
|
-
|
|
66
|
-
for (const tier of declared ?? [])
|
|
67
|
-
byId.set(tier.id, tier);
|
|
68
|
-
return [...byId.values()];
|
|
65
|
+
return [...(declared ?? DEFAULT_TIERS)];
|
|
69
66
|
}
|
|
70
67
|
// ---------------------------------------------------------------------------
|
|
71
68
|
// Task commands.
|
package/dist/testing.js
CHANGED
|
@@ -169,7 +169,7 @@ export async function exerciseAdapter(adapter, context, options = {}) {
|
|
|
169
169
|
let disconnectWasClean = false;
|
|
170
170
|
try {
|
|
171
171
|
authenticationState = await authentication.state();
|
|
172
|
-
const tiers = effectiveTiers(adapter.manifest.
|
|
172
|
+
const tiers = effectiveTiers(adapter.manifest.orgTiers).map(tier => tier.id);
|
|
173
173
|
violations.push(...validateAuthenticationState(authenticationState, "authentication", { tiers }));
|
|
174
174
|
if (authenticationState.status !== "authenticated") {
|
|
175
175
|
throw new Error(`Adapter contract exercise requires authenticated test state, received ${authenticationState.status}`);
|
package/dist/validation.js
CHANGED
|
@@ -311,25 +311,35 @@ export function validateManifest(manifest, path = "manifest") {
|
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
|
-
if (manifest.
|
|
315
|
-
if (!Array.isArray(manifest.
|
|
316
|
-
into.add("manifest.
|
|
314
|
+
if (manifest.orgTiers !== undefined) {
|
|
315
|
+
if (!Array.isArray(manifest.orgTiers)) {
|
|
316
|
+
into.add("manifest.orgTiers.shape", `${path}.orgTiers`, "orgTiers must be an array when present");
|
|
317
317
|
}
|
|
318
318
|
else {
|
|
319
319
|
const ids = new Set();
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
let wellFormed = true;
|
|
321
|
+
manifest.orgTiers.forEach((tier, index) => {
|
|
322
|
+
const at = `${path}.orgTiers[${index}]`;
|
|
322
323
|
if (!isPlainObject(tier)) {
|
|
323
|
-
into.add("manifest.
|
|
324
|
+
into.add("manifest.orgTier.shape", at, "each tier must be an object with an id and a label");
|
|
325
|
+
wellFormed = false;
|
|
324
326
|
return;
|
|
325
327
|
}
|
|
326
|
-
if (into.filled(tier.id, "manifest.
|
|
327
|
-
if (ids.has(tier.id))
|
|
328
|
-
into.add("manifest.
|
|
328
|
+
if (into.filled(tier.id, "manifest.orgTier.id", `${at}.id`, "a tier needs an id")) {
|
|
329
|
+
if (ids.has(tier.id)) {
|
|
330
|
+
into.add("manifest.orgTier.unique", `${at}.id`, `duplicate tier: ${tier.id}`);
|
|
331
|
+
wellFormed = false;
|
|
332
|
+
}
|
|
329
333
|
ids.add(tier.id);
|
|
330
334
|
}
|
|
331
|
-
|
|
335
|
+
else
|
|
336
|
+
wellFormed = false;
|
|
337
|
+
if (!into.filled(tier.label, "manifest.orgTier.label", `${at}.label`, "a tier needs the label a desk shows for it"))
|
|
338
|
+
wellFormed = false;
|
|
332
339
|
});
|
|
340
|
+
if (wellFormed && !ids.has("person")) {
|
|
341
|
+
into.add("manifest.orgTiers.person", `${path}.orgTiers`, "a declared ladder states the whole ladder and must include person, the subject of every resolution");
|
|
342
|
+
}
|
|
333
343
|
}
|
|
334
344
|
}
|
|
335
345
|
if (manifest.taskTypePresentation !== undefined) {
|
|
@@ -488,7 +498,7 @@ function validateLockedByInto(value, rule, path, tiers, into) {
|
|
|
488
498
|
if (!into.filled(value, rule, path, "lockedBy names the tier that locked it"))
|
|
489
499
|
return;
|
|
490
500
|
into.require(value !== "person", `${rule}.person`, path, "a person never locks their own value");
|
|
491
|
-
into.require(tierIds(tiers).includes(value), `${rule}.unknown`, path, `${String(value)} is not a tier this manifest declares:
|
|
501
|
+
into.require(tierIds(tiers).includes(value), `${rule}.unknown`, path, `${String(value)} is not a tier this manifest declares: in force are ${tierIds(tiers).join(", ")}`);
|
|
492
502
|
}
|
|
493
503
|
/** `{ lockedBy, reason? }` standing in for a value: who locked it, and a reason if given. */
|
|
494
504
|
function validateLockedInto(value, rule, path, tiers, into) {
|
|
@@ -512,9 +522,9 @@ function validateResolvedInto(value, rule, path, tiers, into) {
|
|
|
512
522
|
function manifestTiers(manifest) {
|
|
513
523
|
if (!isPlainObject(manifest))
|
|
514
524
|
return undefined;
|
|
515
|
-
const declared = Array.isArray(manifest.
|
|
516
|
-
? manifest.
|
|
517
|
-
:
|
|
525
|
+
const declared = Array.isArray(manifest.orgTiers)
|
|
526
|
+
? manifest.orgTiers.filter((tier) => isPlainObject(tier) && typeof tier.id === "string")
|
|
527
|
+
: undefined;
|
|
518
528
|
return effectiveTiers(declared).map(tier => tier.id);
|
|
519
529
|
}
|
|
520
530
|
const CUSTOM_RENDERS = membersOf({ inline: true, page: true });
|
package/guide.md
CHANGED
|
@@ -176,7 +176,7 @@ type Manifest<C extends Channel = Channel> = {
|
|
|
176
176
|
idleCapabilities?: IdleCapabilities<C>;
|
|
177
177
|
phaseLabels?: TaskPhaseLabels;
|
|
178
178
|
taskTypePresentation?: Record<string, TaskTypePresentation>;
|
|
179
|
-
|
|
179
|
+
orgTiers?: TierDeclaration[];
|
|
180
180
|
};
|
|
181
181
|
```
|
|
182
182
|
|
|
@@ -708,13 +708,17 @@ the person settles the value for everyone below it, and where nothing enforces t
|
|
|
708
708
|
tier that says anything wins. The protocol names a tier by the id the manifest declares for it and
|
|
709
709
|
never describes the chain between them: which tiers a person passes through is the structure's to
|
|
710
710
|
know. A typical organisation has four, and they are the defaults — `DEFAULT_TIERS`: `org`, `site`,
|
|
711
|
-
`team`, `person`, each with the label a desk shows
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
`
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
711
|
+
`team`, `person`, each with the label a desk shows. A structure that differs states its whole
|
|
712
|
+
ladder in `Manifest.orgTiers`, `person` included: what the list carries is in force, and what it
|
|
713
|
+
leaves out does not exist — a structure with no site tier declares `org`, `team`, `person`, and
|
|
714
|
+
`site` is refused on its wire. A declared tier is one the provider's own store actually resolves
|
|
715
|
+
at: a label with no policy behind it decides nothing. A manifest that declares none has exactly
|
|
716
|
+
the four. `lockedBy` is any tier in force except `person`, who never locks their own value;
|
|
717
|
+
`setBy` is any tier in force, or `provisioning`, the protocol's word for "no tier has said
|
|
718
|
+
anything and the provider's own configuration supplied the value" — the provider speaking, never
|
|
719
|
+
Omni's provisioning file, which does not reach the wire. A host renders "who decided" from the
|
|
720
|
+
declared labels and needs no others, and validates every republished `authenticated` state
|
|
721
|
+
against them, not only the sign-in. What the wire carries is the resolution:
|
|
718
722
|
|
|
719
723
|
- **`lockedBy`** — a tier above the person made this value theirs to keep. A person never locks
|
|
720
724
|
their own value, and the queue is not a tier: what the queue does not allow at all is absent.
|
|
@@ -1349,7 +1353,7 @@ compile time.
|
|
|
1349
1353
|
| `idleCapabilities` | Declares actions Omni may offer while the agent has no active task, such as voice dialing. Task controls do not belong here. |
|
|
1350
1354
|
| `phaseLabels` | Optional static adapter-defined display names for canonical `TaskPhase` values. They cannot vary at runtime. |
|
|
1351
1355
|
| `taskTypePresentation` | Optional static adapter-defined presentation keyed by exact `taskType`. It names the item and its optional agent-facing reference. |
|
|
1352
|
-
| `
|
|
1356
|
+
| `orgTiers` | The organisation's whole ladder as the provider calls it, each tier with the label a desk shows for "who decided". Stated outright, `person` included: what it leaves out does not exist. Omitted for the typical four, `DEFAULT_TIERS`. See **Who decides what an agent may do**. |
|
|
1353
1357
|
|
|
1354
1358
|
### Authentication methods
|
|
1355
1359
|
|
|
@@ -1867,7 +1871,7 @@ Returns the provider's complete authoritative state at one point in time.
|
|
|
1867
1871
|
order.
|
|
1868
1872
|
- `tasks` must contain every task currently owned by this agent for this provider.
|
|
1869
1873
|
- A snapshot replaces Omni's state for this provider; it is not a partial patch.
|
|
1870
|
-
- The adapter may return synchronously when it already holds current live values,
|
|
1874
|
+
- The adapter may return synchronously when it already holds current live values, or
|
|
1871
1875
|
asynchronously when it must obtain state.
|
|
1872
1876
|
|
|
1873
1877
|
### `Connection.subscribe(listener)`
|
|
@@ -3568,7 +3572,7 @@ same exported checks are used by Omni and adapter tests so their interpretations
|
|
|
3568
3572
|
| `validateScheduledActivity(activity)` | Required activity fields and start/end ordering. |
|
|
3569
3573
|
| `validateHostReport(report)` | The host's own report as published to an adapter: `online`, and where there is audio, an input that is `ready` with the microphone and `flowing`, or `unavailable` with a reason and the failure that says why, and an output that is `ready` or `unavailable` with its failure. The harness validates whatever host a test hands the adapter; `stillHost(report)` builds one that never changes. |
|
|
3570
3574
|
| `validateResult(result, method)` | What a connection method answered: the status it gives, a failure where the status says so and nowhere else, the failure's shape, and that an `omni.` code is one this contract names. |
|
|
3571
|
-
| `validateAuthenticationState(state)` | The identity each state must carry, the capabilities a usable login declares, and the expiry that only `authenticated` may. |
|
|
3575
|
+
| `validateAuthenticationState(state)` | The identity each state must carry, the capabilities a usable login declares, and the expiry that only `authenticated` may. Omni applies it to every state a session publishes — the republished as much as the first. |
|
|
3572
3576
|
|
|
3573
3577
|
Each returns `ProtocolViolation[]` rather than throwing, so a caller can report every problem at
|
|
3574
3578
|
once. A violation carries a stable `rule` id such as `task.browser.url.scheme`, the `path` it was
|