@nanobpm/agentic 0.1.0
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/README.md +22 -0
- package/dist/blackboard/family.d.ts +40 -0
- package/dist/blackboard/family.js +151 -0
- package/dist/blackboard/index.d.ts +21 -0
- package/dist/blackboard/index.js +19 -0
- package/dist/blackboard/schema.d.ts +30 -0
- package/dist/blackboard/schema.js +42 -0
- package/dist/blackboard/store.d.ts +138 -0
- package/dist/blackboard/store.js +216 -0
- package/dist/blackboard/test-db.d.ts +5 -0
- package/dist/blackboard/test-db.js +42 -0
- package/dist/channel/auth.d.ts +41 -0
- package/dist/channel/auth.js +67 -0
- package/dist/channel/clock.d.ts +11 -0
- package/dist/channel/clock.js +4 -0
- package/dist/channel/connection.d.ts +75 -0
- package/dist/channel/connection.js +14 -0
- package/dist/channel/dispatch.d.ts +46 -0
- package/dist/channel/dispatch.js +86 -0
- package/dist/channel/hub.d.ts +78 -0
- package/dist/channel/hub.js +157 -0
- package/dist/channel/index.d.ts +27 -0
- package/dist/channel/index.js +20 -0
- package/dist/channel/registry.d.ts +68 -0
- package/dist/channel/registry.js +84 -0
- package/dist/channel/ws-transport.d.ts +23 -0
- package/dist/channel/ws-transport.js +178 -0
- package/dist/cockpit/boot.d.ts +68 -0
- package/dist/cockpit/boot.js +202 -0
- package/dist/cockpit/fake-dom.d.ts +37 -0
- package/dist/cockpit/fake-dom.js +73 -0
- package/dist/cockpit/index.d.ts +27 -0
- package/dist/cockpit/index.js +27 -0
- package/dist/cockpit/relay-client.d.ts +52 -0
- package/dist/cockpit/relay-client.js +192 -0
- package/dist/cockpit/render.d.ts +58 -0
- package/dist/cockpit/render.js +122 -0
- package/dist/cockpit/terminal-session.d.ts +95 -0
- package/dist/cockpit/terminal-session.js +123 -0
- package/dist/cockpit/view.d.ts +79 -0
- package/dist/cockpit/view.js +58 -0
- package/dist/demand/c8-rest.d.ts +77 -0
- package/dist/demand/c8-rest.js +123 -0
- package/dist/demand/index.d.ts +24 -0
- package/dist/demand/index.js +24 -0
- package/dist/demand/model.d.ts +68 -0
- package/dist/demand/model.js +118 -0
- package/dist/demand/taskdef.d.ts +40 -0
- package/dist/demand/taskdef.js +67 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/presence/family.d.ts +40 -0
- package/dist/presence/family.js +166 -0
- package/dist/presence/index.d.ts +19 -0
- package/dist/presence/index.js +17 -0
- package/dist/presence/schema.d.ts +20 -0
- package/dist/presence/schema.js +32 -0
- package/dist/presence/store.d.ts +130 -0
- package/dist/presence/store.js +191 -0
- package/dist/presence/test-db.d.ts +5 -0
- package/dist/presence/test-db.js +42 -0
- package/dist/protocol/conformance/frames.d.ts +24 -0
- package/dist/protocol/conformance/frames.js +116 -0
- package/dist/protocol/conformance/index.d.ts +13 -0
- package/dist/protocol/conformance/index.js +13 -0
- package/dist/protocol/conformance/malformed.d.ts +14 -0
- package/dist/protocol/conformance/malformed.js +44 -0
- package/dist/protocol/conformance/tokens.d.ts +19 -0
- package/dist/protocol/conformance/tokens.js +49 -0
- package/dist/protocol/conformance/vocab.d.ts +23 -0
- package/dist/protocol/conformance/vocab.js +97 -0
- package/dist/protocol/families.d.ts +32 -0
- package/dist/protocol/families.js +45 -0
- package/dist/protocol/frame.d.ts +45 -0
- package/dist/protocol/frame.js +114 -0
- package/dist/protocol/hex.d.ts +7 -0
- package/dist/protocol/hex.js +26 -0
- package/dist/protocol/index.d.ts +23 -0
- package/dist/protocol/index.js +23 -0
- package/dist/protocol/lanes.d.ts +36 -0
- package/dist/protocol/lanes.js +40 -0
- package/dist/protocol/payloads.d.ts +64 -0
- package/dist/protocol/payloads.js +122 -0
- package/dist/protocol/token.d.ts +34 -0
- package/dist/protocol/token.js +81 -0
- package/dist/protocol/vocab/schema.d.ts +51 -0
- package/dist/protocol/vocab/schema.js +218 -0
- package/dist/relay/incarnation.d.ts +17 -0
- package/dist/relay/incarnation.js +50 -0
- package/dist/relay/index.d.ts +25 -0
- package/dist/relay/index.js +22 -0
- package/dist/relay/relay-family.d.ts +68 -0
- package/dist/relay/relay-family.js +272 -0
- package/dist/relay/ring.d.ts +49 -0
- package/dist/relay/ring.js +105 -0
- package/dist/relay/scheduler.d.ts +72 -0
- package/dist/relay/scheduler.js +180 -0
- package/dist/relay/validate.d.ts +29 -0
- package/dist/relay/validate.js +39 -0
- package/dist/transcript/index.d.ts +18 -0
- package/dist/transcript/index.js +17 -0
- package/dist/transcript/schema.d.ts +32 -0
- package/dist/transcript/schema.js +48 -0
- package/dist/transcript/store.d.ts +192 -0
- package/dist/transcript/store.js +347 -0
- package/dist/transcript/test-db.d.ts +5 -0
- package/dist/transcript/test-db.js +41 -0
- package/dist/vocab/core-vocab.d.ts +26 -0
- package/dist/vocab/core-vocab.js +67 -0
- package/dist/vocab/diversity.d.ts +78 -0
- package/dist/vocab/diversity.js +89 -0
- package/dist/vocab/index.d.ts +22 -0
- package/dist/vocab/index.js +22 -0
- package/dist/vocab/merge.d.ts +9 -0
- package/dist/vocab/merge.js +104 -0
- package/dist/vocab/requires.d.ts +49 -0
- package/dist/vocab/requires.js +107 -0
- package/dist/vocab/resolver.d.ts +62 -0
- package/dist/vocab/resolver.js +149 -0
- package/dist/vocab/serve.d.ts +39 -0
- package/dist/vocab/serve.js +36 -0
- package/package.json +108 -0
- package/page/cockpit.css +114 -0
- package/page/cockpit.page.json +33 -0
- package/page/embed.html +40 -0
- package/page/mount.js +78 -0
- package/page/standalone.html +43 -0
- package/src/blackboard/family.test.ts +280 -0
- package/src/blackboard/family.ts +208 -0
- package/src/blackboard/index.ts +42 -0
- package/src/blackboard/schema.test.ts +60 -0
- package/src/blackboard/schema.ts +44 -0
- package/src/blackboard/store.test.ts +189 -0
- package/src/blackboard/store.ts +331 -0
- package/src/blackboard/test-db.ts +47 -0
- package/src/channel/auth.test.ts +64 -0
- package/src/channel/auth.ts +101 -0
- package/src/channel/clock.ts +14 -0
- package/src/channel/connection.ts +77 -0
- package/src/channel/dispatch.test.ts +83 -0
- package/src/channel/dispatch.ts +102 -0
- package/src/channel/hub.test.ts +335 -0
- package/src/channel/hub.ts +222 -0
- package/src/channel/index.ts +55 -0
- package/src/channel/registry.test.ts +73 -0
- package/src/channel/registry.ts +137 -0
- package/src/channel/ws-transport.test.ts +234 -0
- package/src/channel/ws-transport.ts +212 -0
- package/src/cockpit/boot.test.ts +374 -0
- package/src/cockpit/boot.ts +280 -0
- package/src/cockpit/fake-dom.ts +90 -0
- package/src/cockpit/index.ts +63 -0
- package/src/cockpit/relay-client.test.ts +359 -0
- package/src/cockpit/relay-client.ts +234 -0
- package/src/cockpit/render.test.ts +149 -0
- package/src/cockpit/render.ts +194 -0
- package/src/cockpit/terminal-session.test.ts +252 -0
- package/src/cockpit/terminal-session.ts +194 -0
- package/src/cockpit/view.test.ts +117 -0
- package/src/cockpit/view.ts +140 -0
- package/src/demand/c8-rest.test.ts +140 -0
- package/src/demand/c8-rest.ts +167 -0
- package/src/demand/index.ts +42 -0
- package/src/demand/model.test.ts +197 -0
- package/src/demand/model.ts +183 -0
- package/src/demand/taskdef.test.ts +85 -0
- package/src/demand/taskdef.ts +78 -0
- package/src/index.ts +17 -0
- package/src/presence/family.test.ts +252 -0
- package/src/presence/family.ts +205 -0
- package/src/presence/index.ts +26 -0
- package/src/presence/schema.test.ts +53 -0
- package/src/presence/schema.ts +34 -0
- package/src/presence/store.test.ts +190 -0
- package/src/presence/store.ts +287 -0
- package/src/presence/test-db.test.ts +57 -0
- package/src/presence/test-db.ts +47 -0
- package/src/protocol/conformance/corpus.test.ts +66 -0
- package/src/protocol/conformance/frames.ts +142 -0
- package/src/protocol/conformance/index.ts +29 -0
- package/src/protocol/conformance/malformed.ts +59 -0
- package/src/protocol/conformance/tokens.ts +70 -0
- package/src/protocol/conformance/vocab.ts +122 -0
- package/src/protocol/families.ts +54 -0
- package/src/protocol/frame.test.ts +116 -0
- package/src/protocol/frame.ts +171 -0
- package/src/protocol/hex.ts +28 -0
- package/src/protocol/index.ts +84 -0
- package/src/protocol/lanes.test.ts +82 -0
- package/src/protocol/lanes.ts +54 -0
- package/src/protocol/payloads.test.ts +91 -0
- package/src/protocol/payloads.ts +201 -0
- package/src/protocol/token.test.ts +57 -0
- package/src/protocol/token.ts +123 -0
- package/src/protocol/vocab/schema.test.ts +67 -0
- package/src/protocol/vocab/schema.ts +281 -0
- package/src/relay/incarnation.test.ts +53 -0
- package/src/relay/incarnation.ts +54 -0
- package/src/relay/index.ts +34 -0
- package/src/relay/integration.test.ts +135 -0
- package/src/relay/relay-family.test.ts +236 -0
- package/src/relay/relay-family.ts +336 -0
- package/src/relay/ring.test.ts +138 -0
- package/src/relay/ring.ts +136 -0
- package/src/relay/scheduler.test.ts +233 -0
- package/src/relay/scheduler.ts +208 -0
- package/src/relay/validate.test.ts +43 -0
- package/src/relay/validate.ts +44 -0
- package/src/transcript/index.ts +33 -0
- package/src/transcript/integration.test.ts +108 -0
- package/src/transcript/schema.test.ts +69 -0
- package/src/transcript/schema.ts +51 -0
- package/src/transcript/store.test.ts +285 -0
- package/src/transcript/store.ts +530 -0
- package/src/transcript/test-db.ts +46 -0
- package/src/vocab/core-vocab.test.ts +34 -0
- package/src/vocab/core-vocab.ts +88 -0
- package/src/vocab/diversity.test.ts +153 -0
- package/src/vocab/diversity.ts +169 -0
- package/src/vocab/index.ts +55 -0
- package/src/vocab/merge.test.ts +73 -0
- package/src/vocab/merge.ts +117 -0
- package/src/vocab/requires.test.ts +69 -0
- package/src/vocab/requires.ts +155 -0
- package/src/vocab/resolver.test.ts +118 -0
- package/src/vocab/resolver.ts +187 -0
- package/src/vocab/serve.test.ts +64 -0
- package/src/vocab/serve.ts +66 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** The current core-vocabulary artifact version. */
|
|
2
|
+
export const CORE_VOCAB_VERSION = 1;
|
|
3
|
+
/**
|
|
4
|
+
* The frozen core vocabulary document. Deep-frozen so a consumer cannot mutate
|
|
5
|
+
* the shared artifact; author extensions go through {@link mergeVocab}, which
|
|
6
|
+
* returns a fresh document.
|
|
7
|
+
*/
|
|
8
|
+
export const CORE_VOCAB = deepFreeze({
|
|
9
|
+
version: CORE_VOCAB_VERSION,
|
|
10
|
+
networks: {
|
|
11
|
+
planning: {
|
|
12
|
+
roles: {
|
|
13
|
+
planner: { requires: ["cognition=planning"], weight: 5, seats: 1 },
|
|
14
|
+
reviewer: {
|
|
15
|
+
requires: ["cognition=planning"],
|
|
16
|
+
weight: 4,
|
|
17
|
+
seats: ["red", "blue"],
|
|
18
|
+
seatsDistinctFamily: true,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
qa: {
|
|
23
|
+
roles: {
|
|
24
|
+
tester: { requires: ["cognition=qa"], weight: 3, seats: 2 },
|
|
25
|
+
reviewer: {
|
|
26
|
+
requires: ["cognition=qa"],
|
|
27
|
+
weight: 3,
|
|
28
|
+
seats: ["red", "blue"],
|
|
29
|
+
seatsDistinctFamily: true,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
implementation: {
|
|
34
|
+
roles: {
|
|
35
|
+
senior: { requires: ["cognition=implementation", "weight>=4"], weight: 5, seats: 1 },
|
|
36
|
+
junior: { requires: ["cognition=implementation"], weight: 2, seats: 3 },
|
|
37
|
+
reviewer: {
|
|
38
|
+
requires: ["cognition=implementation"],
|
|
39
|
+
weight: 4,
|
|
40
|
+
seats: ["red", "blue"],
|
|
41
|
+
seatsDistinctFamily: true,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
ci: {
|
|
46
|
+
roles: {
|
|
47
|
+
runner: { requires: ["cognition=ci"], weight: 1, seats: 1 },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
// The bare `decide` role (single-segment token): a self-named top-level role
|
|
51
|
+
// the resolver collapses to the network-less token `decide`.
|
|
52
|
+
decide: {
|
|
53
|
+
roles: {
|
|
54
|
+
decide: { requires: ["cognition=decide"], weight: 5, seats: 1 },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
function deepFreeze(value) {
|
|
60
|
+
if (value !== null && typeof value === "object") {
|
|
61
|
+
for (const child of Object.values(value)) {
|
|
62
|
+
deepFreeze(child);
|
|
63
|
+
}
|
|
64
|
+
Object.freeze(value);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The diversity SLO — `family(#red) ≠ family(#blue)`.
|
|
3
|
+
*
|
|
4
|
+
* A role may declare `seatsDistinctFamily: true` (STRICT opt-in) to require its
|
|
5
|
+
* seats be filled by distinct families. The SLO grades an assignment of seats to
|
|
6
|
+
* families red / amber / green:
|
|
7
|
+
*
|
|
8
|
+
* - GREEN — no role has two seats sharing a family.
|
|
9
|
+
* - AMBER — a same-family collision on a WARN-DEFAULT role (one that did NOT
|
|
10
|
+
* opt into `seatsDistinctFamily`): tolerated, but surfaced.
|
|
11
|
+
* - RED — a same-family collision on a STRICT role (`seatsDistinctFamily`):
|
|
12
|
+
* an SLO violation.
|
|
13
|
+
*
|
|
14
|
+
* Warn-default is the point of the "strict opt-in per role": every role wants
|
|
15
|
+
* family diversity, but only a role that opts in makes a collision RED; elsewhere
|
|
16
|
+
* a collision is an AMBER warning, never a hard failure.
|
|
17
|
+
*
|
|
18
|
+
* The assignment can be given explicitly (seat→family) or CORRELATED from the S2
|
|
19
|
+
* presence registry: {@link correlateRegistry} resolves each registered worker's
|
|
20
|
+
* capability to the roles it may fill and seats them deterministically, so the
|
|
21
|
+
* live registry's family mix is graded against the same SLO.
|
|
22
|
+
*/
|
|
23
|
+
import type { Capability } from "../protocol/index.ts";
|
|
24
|
+
import type { VocabResolver } from "./resolver.ts";
|
|
25
|
+
export type DiversityStatus = "green" | "amber" | "red";
|
|
26
|
+
/** One seat of a role filled by a worker of a given family. */
|
|
27
|
+
export interface SeatAssignment {
|
|
28
|
+
/** The seat label (named seat, or a synthesised index for counted seats). */
|
|
29
|
+
readonly seat: string;
|
|
30
|
+
/** The enrolment family occupying the seat. */
|
|
31
|
+
readonly family: string;
|
|
32
|
+
/** The worker instance occupying the seat, when known (registry correlation). */
|
|
33
|
+
readonly instance?: string;
|
|
34
|
+
}
|
|
35
|
+
/** The diversity grade for a single role. */
|
|
36
|
+
export interface RoleDiversity {
|
|
37
|
+
/** The role's routing token. */
|
|
38
|
+
readonly token: string;
|
|
39
|
+
/** Whether the role opted into strict distinct-family seating. */
|
|
40
|
+
readonly seatsDistinctFamily: boolean;
|
|
41
|
+
/** The seats considered, in seat order. */
|
|
42
|
+
readonly assignments: readonly SeatAssignment[];
|
|
43
|
+
/** Families that fill more than one seat of this role (the collisions). */
|
|
44
|
+
readonly collidingFamilies: readonly string[];
|
|
45
|
+
/** This role's grade. */
|
|
46
|
+
readonly status: DiversityStatus;
|
|
47
|
+
}
|
|
48
|
+
/** The overall diversity report across every graded role. */
|
|
49
|
+
export interface DiversityReport {
|
|
50
|
+
/** The worst grade across all roles (red > amber > green). */
|
|
51
|
+
readonly status: DiversityStatus;
|
|
52
|
+
/** Per-role grades, sorted by token. */
|
|
53
|
+
readonly roles: readonly RoleDiversity[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Grade an explicit seat assignment. `assignments` maps a role's routing token
|
|
57
|
+
* to the families seated in it. Only roles known to the resolver are graded; an
|
|
58
|
+
* unknown token is ignored (there is nothing to grade it against).
|
|
59
|
+
*/
|
|
60
|
+
export declare function computeDiversity(resolver: VocabResolver, assignments: ReadonlyMap<string, readonly SeatAssignment[]>): DiversityReport;
|
|
61
|
+
/** A registered worker as seen on the S2 presence registry (structural). */
|
|
62
|
+
export interface RegisteredWorker {
|
|
63
|
+
/** The worker instance id. */
|
|
64
|
+
readonly instance: string;
|
|
65
|
+
/** The declared enrolment capability (its `family` fills a seat). */
|
|
66
|
+
readonly capability: Capability;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Correlate the live S2 registry against the vocab and grade its diversity.
|
|
70
|
+
*
|
|
71
|
+
* Each registered worker is resolved to the roles it may fill; for every role,
|
|
72
|
+
* the workers that qualify are seated deterministically (sorted by instance)
|
|
73
|
+
* into the role's seats, and the resulting family mix is graded by
|
|
74
|
+
* {@link computeDiversity}. Workers with no declared family, and roles with no
|
|
75
|
+
* qualifying worker, are skipped. Overflow workers beyond a role's seat count do
|
|
76
|
+
* not take a seat (they are surplus supply, not a diversity collision).
|
|
77
|
+
*/
|
|
78
|
+
export declare function correlateRegistry(resolver: VocabResolver, workers: readonly RegisteredWorker[]): DiversityReport;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const SEVERITY = { green: 0, amber: 1, red: 2 };
|
|
2
|
+
function worst(a, b) {
|
|
3
|
+
return SEVERITY[a] >= SEVERITY[b] ? a : b;
|
|
4
|
+
}
|
|
5
|
+
function collidingFamilies(assignments) {
|
|
6
|
+
const counts = new Map();
|
|
7
|
+
for (const { family } of assignments) {
|
|
8
|
+
counts.set(family, (counts.get(family) ?? 0) + 1);
|
|
9
|
+
}
|
|
10
|
+
const colliding = [];
|
|
11
|
+
for (const [family, count] of counts) {
|
|
12
|
+
if (count > 1)
|
|
13
|
+
colliding.push(family);
|
|
14
|
+
}
|
|
15
|
+
colliding.sort();
|
|
16
|
+
return colliding;
|
|
17
|
+
}
|
|
18
|
+
function gradeRole(role, assignments) {
|
|
19
|
+
const colliding = collidingFamilies(assignments);
|
|
20
|
+
const status = colliding.length === 0 ? "green" : role.seatsDistinctFamily ? "red" : "amber";
|
|
21
|
+
return {
|
|
22
|
+
token: role.token,
|
|
23
|
+
seatsDistinctFamily: role.seatsDistinctFamily,
|
|
24
|
+
assignments,
|
|
25
|
+
collidingFamilies: colliding,
|
|
26
|
+
status,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Grade an explicit seat assignment. `assignments` maps a role's routing token
|
|
31
|
+
* to the families seated in it. Only roles known to the resolver are graded; an
|
|
32
|
+
* unknown token is ignored (there is nothing to grade it against).
|
|
33
|
+
*/
|
|
34
|
+
export function computeDiversity(resolver, assignments) {
|
|
35
|
+
const roles = [];
|
|
36
|
+
let status = "green";
|
|
37
|
+
for (const [token, seatAssignments] of assignments) {
|
|
38
|
+
const role = resolver.roleForToken(token);
|
|
39
|
+
if (role === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
const graded = gradeRole(role, seatAssignments);
|
|
42
|
+
roles.push(graded);
|
|
43
|
+
status = worst(status, graded.status);
|
|
44
|
+
}
|
|
45
|
+
roles.sort((a, b) => (a.token < b.token ? -1 : a.token > b.token ? 1 : 0));
|
|
46
|
+
return { status, roles };
|
|
47
|
+
}
|
|
48
|
+
/** Seat labels for a role: its named seats, or synthesised `0..n-1` for a count. */
|
|
49
|
+
function seatLabels(role) {
|
|
50
|
+
if (typeof role.seats === "number") {
|
|
51
|
+
return Array.from({ length: role.seats }, (_unused, index) => String(index));
|
|
52
|
+
}
|
|
53
|
+
return [...role.seats];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Correlate the live S2 registry against the vocab and grade its diversity.
|
|
57
|
+
*
|
|
58
|
+
* Each registered worker is resolved to the roles it may fill; for every role,
|
|
59
|
+
* the workers that qualify are seated deterministically (sorted by instance)
|
|
60
|
+
* into the role's seats, and the resulting family mix is graded by
|
|
61
|
+
* {@link computeDiversity}. Workers with no declared family, and roles with no
|
|
62
|
+
* qualifying worker, are skipped. Overflow workers beyond a role's seat count do
|
|
63
|
+
* not take a seat (they are surplus supply, not a diversity collision).
|
|
64
|
+
*/
|
|
65
|
+
export function correlateRegistry(resolver, workers) {
|
|
66
|
+
const sorted = [...workers].sort((a, b) => (a.instance < b.instance ? -1 : a.instance > b.instance ? 1 : 0));
|
|
67
|
+
// Resolve each worker's SERVE token set once (O(workers)) so per-role seating
|
|
68
|
+
// is a membership check, not a repeated resolve — correlation stays O(workers × roles).
|
|
69
|
+
const workerTokens = sorted.map((worker) => new Set(resolver.resolve(worker.capability).tokens));
|
|
70
|
+
const perRole = new Map();
|
|
71
|
+
for (const role of resolver.roles()) {
|
|
72
|
+
const seats = seatLabels(role);
|
|
73
|
+
if (seats.length === 0)
|
|
74
|
+
continue;
|
|
75
|
+
const qualifying = sorted.filter((worker, index) => worker.capability.family !== undefined && workerTokens[index].has(role.token));
|
|
76
|
+
const assignments = [];
|
|
77
|
+
for (let index = 0; index < seats.length && index < qualifying.length; index += 1) {
|
|
78
|
+
const worker = qualifying[index];
|
|
79
|
+
const family = worker.capability.family;
|
|
80
|
+
if (family === undefined)
|
|
81
|
+
continue;
|
|
82
|
+
assignments.push({ seat: seats[index], family, instance: worker.instance });
|
|
83
|
+
}
|
|
84
|
+
if (assignments.length > 0) {
|
|
85
|
+
perRole.set(role.token, assignments);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return computeDiversity(resolver, perRole);
|
|
89
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nanobpm/agentic-vocab — the vocab resolver & core vocabulary for the Nano
|
|
3
|
+
* agentic protocol (ADR 0056, slice S3).
|
|
4
|
+
*
|
|
5
|
+
* Turns the versioned vocab artifact (the ONE capability→token map) into the
|
|
6
|
+
* REGISTER→SERVE handshake: a declared enrolment capability resolves to a
|
|
7
|
+
* deterministic SERVE token set ({@link VocabResolver}); the opinionated core
|
|
8
|
+
* vocabulary ships working out of the box ({@link CORE_VOCAB}); authors extend it
|
|
9
|
+
* in the same schema ({@link mergeVocab}); and the diversity SLO grades seating
|
|
10
|
+
* red / amber / green ({@link computeDiversity} / {@link correlateRegistry}).
|
|
11
|
+
*
|
|
12
|
+
* The wire contract (family set, token grammar, vocab schema, `serve` payload)
|
|
13
|
+
* lives in `@nanobpm/agentic-protocol`; this package builds on it and never
|
|
14
|
+
* redefines it. Capability is NEVER in the routing token — it is the enrolment
|
|
15
|
+
* attribute the `requires` gate reads.
|
|
16
|
+
*/
|
|
17
|
+
export { VocabResolver, VocabDocumentError, type Resolution, type ResolvedRole, } from "./resolver.ts";
|
|
18
|
+
export { CORE_VOCAB, CORE_VOCAB_VERSION } from "./core-vocab.ts";
|
|
19
|
+
export { mergeVocab } from "./merge.ts";
|
|
20
|
+
export { REQUIRES_FIELDS, RequiresParseError, parseRequires, parseRequiresList, satisfiesRequires, satisfiesPredicate, type RequiresField, type RequiresOp, type RequiresPredicate, } from "./requires.ts";
|
|
21
|
+
export { computeDiversity, correlateRegistry, type DiversityStatus, type DiversityReport, type RoleDiversity, type SeatAssignment, type RegisteredWorker, } from "./diversity.ts";
|
|
22
|
+
export { buildServePayload, buildServeFrame, serveCapability, type ServeSink, } from "./serve.ts";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nanobpm/agentic-vocab — the vocab resolver & core vocabulary for the Nano
|
|
3
|
+
* agentic protocol (ADR 0056, slice S3).
|
|
4
|
+
*
|
|
5
|
+
* Turns the versioned vocab artifact (the ONE capability→token map) into the
|
|
6
|
+
* REGISTER→SERVE handshake: a declared enrolment capability resolves to a
|
|
7
|
+
* deterministic SERVE token set ({@link VocabResolver}); the opinionated core
|
|
8
|
+
* vocabulary ships working out of the box ({@link CORE_VOCAB}); authors extend it
|
|
9
|
+
* in the same schema ({@link mergeVocab}); and the diversity SLO grades seating
|
|
10
|
+
* red / amber / green ({@link computeDiversity} / {@link correlateRegistry}).
|
|
11
|
+
*
|
|
12
|
+
* The wire contract (family set, token grammar, vocab schema, `serve` payload)
|
|
13
|
+
* lives in `@nanobpm/agentic-protocol`; this package builds on it and never
|
|
14
|
+
* redefines it. Capability is NEVER in the routing token — it is the enrolment
|
|
15
|
+
* attribute the `requires` gate reads.
|
|
16
|
+
*/
|
|
17
|
+
export { VocabResolver, VocabDocumentError, } from "./resolver.js";
|
|
18
|
+
export { CORE_VOCAB, CORE_VOCAB_VERSION } from "./core-vocab.js";
|
|
19
|
+
export { mergeVocab } from "./merge.js";
|
|
20
|
+
export { REQUIRES_FIELDS, RequiresParseError, parseRequires, parseRequiresList, satisfiesRequires, satisfiesPredicate, } from "./requires.js";
|
|
21
|
+
export { computeDiversity, correlateRegistry, } from "./diversity.js";
|
|
22
|
+
export { buildServePayload, buildServeFrame, serveCapability, } from "./serve.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { VocabDocument } from "../protocol/index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Merge an author `extension` over a `base` vocab (default {@link CORE_VOCAB}),
|
|
4
|
+
* returning a fresh, re-validated {@link VocabDocument}.
|
|
5
|
+
*
|
|
6
|
+
* @throws VocabDocumentError if either input or the merged result is not a valid
|
|
7
|
+
* vocab artifact.
|
|
8
|
+
*/
|
|
9
|
+
export declare function mergeVocab(extension: VocabDocument, base?: VocabDocument): VocabDocument;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author-extension merge — extend the core vocabulary in the SAME schema.
|
|
3
|
+
*
|
|
4
|
+
* Authors do not get a second schema for extensions (S0 invariant 6): they hand
|
|
5
|
+
* a {@link VocabDocument} in the exact core shape and {@link mergeVocab} deep-
|
|
6
|
+
* merges it over a base (the {@link CORE_VOCAB} by default). The merge is
|
|
7
|
+
* structural and deterministic:
|
|
8
|
+
*
|
|
9
|
+
* - networks / subnetworks are merged recursively (union of names);
|
|
10
|
+
* - a role present in both is merged field-by-field, the extension winning per
|
|
11
|
+
* field, so an author can retune one attribute (e.g. bump `weight` or add a
|
|
12
|
+
* `seatsDistinctFamily`) without restating the whole role;
|
|
13
|
+
* - a role/network present only in the extension is added;
|
|
14
|
+
* - `version` becomes the MAX of the two, so an extension can advance it.
|
|
15
|
+
*
|
|
16
|
+
* The merged document is re-validated against the S0 schema and returned fresh —
|
|
17
|
+
* neither input is mutated, and a merge that produces an invalid artifact throws
|
|
18
|
+
* {@link VocabDocumentError} rather than yielding a subtly broken vocab.
|
|
19
|
+
*/
|
|
20
|
+
import { validateVocabDocument } from "../protocol/index.js";
|
|
21
|
+
import { CORE_VOCAB } from "./core-vocab.js";
|
|
22
|
+
import { VocabDocumentError } from "./resolver.js";
|
|
23
|
+
function mergeRole(base, ext) {
|
|
24
|
+
const merged = {};
|
|
25
|
+
const requires = ext.requires ?? base.requires;
|
|
26
|
+
if (requires !== undefined)
|
|
27
|
+
merged.requires = [...requires];
|
|
28
|
+
const weight = ext.weight ?? base.weight;
|
|
29
|
+
if (weight !== undefined)
|
|
30
|
+
merged.weight = weight;
|
|
31
|
+
const seats = ext.seats ?? base.seats;
|
|
32
|
+
if (seats !== undefined)
|
|
33
|
+
merged.seats = typeof seats === "number" ? seats : [...seats];
|
|
34
|
+
const seatsDistinctFamily = ext.seatsDistinctFamily ?? base.seatsDistinctFamily;
|
|
35
|
+
if (seatsDistinctFamily !== undefined)
|
|
36
|
+
merged.seatsDistinctFamily = seatsDistinctFamily;
|
|
37
|
+
return merged;
|
|
38
|
+
}
|
|
39
|
+
const EMPTY_ROLE = {};
|
|
40
|
+
function cloneRole(role) {
|
|
41
|
+
return mergeRole(EMPTY_ROLE, role);
|
|
42
|
+
}
|
|
43
|
+
function mergeRoles(base, ext) {
|
|
44
|
+
if (base === undefined && ext === undefined)
|
|
45
|
+
return undefined;
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const [name, role] of Object.entries(base ?? {})) {
|
|
48
|
+
out[name] = cloneRole(role);
|
|
49
|
+
}
|
|
50
|
+
for (const [name, role] of Object.entries(ext ?? {})) {
|
|
51
|
+
const existing = out[name];
|
|
52
|
+
out[name] = existing === undefined ? cloneRole(role) : mergeRole(existing, role);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function mergeNetwork(base, ext) {
|
|
57
|
+
const roles = mergeRoles(base?.roles, ext.roles);
|
|
58
|
+
const subnetworks = mergeNetworks(base?.subnetworks, ext.subnetworks);
|
|
59
|
+
const merged = {};
|
|
60
|
+
if (roles !== undefined)
|
|
61
|
+
merged.roles = roles;
|
|
62
|
+
if (subnetworks !== undefined)
|
|
63
|
+
merged.subnetworks = subnetworks;
|
|
64
|
+
return merged;
|
|
65
|
+
}
|
|
66
|
+
function mergeNetworks(base, ext) {
|
|
67
|
+
if (base === undefined && ext === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
const out = {};
|
|
70
|
+
for (const [name, network] of Object.entries(base ?? {})) {
|
|
71
|
+
out[name] = mergeNetwork(undefined, network);
|
|
72
|
+
}
|
|
73
|
+
for (const [name, network] of Object.entries(ext ?? {})) {
|
|
74
|
+
out[name] = mergeNetwork(out[name], network);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Merge an author `extension` over a `base` vocab (default {@link CORE_VOCAB}),
|
|
80
|
+
* returning a fresh, re-validated {@link VocabDocument}.
|
|
81
|
+
*
|
|
82
|
+
* @throws VocabDocumentError if either input or the merged result is not a valid
|
|
83
|
+
* vocab artifact.
|
|
84
|
+
*/
|
|
85
|
+
export function mergeVocab(extension, base = CORE_VOCAB) {
|
|
86
|
+
for (const [label, doc] of [
|
|
87
|
+
["base", base],
|
|
88
|
+
["extension", extension],
|
|
89
|
+
]) {
|
|
90
|
+
const check = validateVocabDocument(doc);
|
|
91
|
+
if (!check.ok) {
|
|
92
|
+
throw new VocabDocumentError(check.errors.map((e) => ({ path: `${label}:${e.path}`, message: e.message })));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const merged = {
|
|
96
|
+
version: Math.max(base.version, extension.version),
|
|
97
|
+
networks: mergeNetworks(base.networks, extension.networks) ?? {},
|
|
98
|
+
};
|
|
99
|
+
const result = validateVocabDocument(merged);
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
throw new VocabDocumentError(result.errors.map((e) => ({ path: `merged:${e.path}`, message: e.message })));
|
|
102
|
+
}
|
|
103
|
+
return result.value;
|
|
104
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The enrolment-capability gate: `requires` predicates.
|
|
3
|
+
*
|
|
4
|
+
* A vocab role's `requires` list is the REGISTRY GATE — it decides WHO may fill
|
|
5
|
+
* the role based on the declared enrolment capability (cognition / weight /
|
|
6
|
+
* family / host). It is deliberately NOT part of the routing token: capability
|
|
7
|
+
* never rides the token (S0 invariant 3); it gates enrolment here.
|
|
8
|
+
*
|
|
9
|
+
* Each `requires` entry is a single predicate over one capability field:
|
|
10
|
+
*
|
|
11
|
+
* predicate = field op value
|
|
12
|
+
* field = cognition | weight | family | host
|
|
13
|
+
* op = "=" | "==" | "!=" | ">=" | "<=" | ">" | "<"
|
|
14
|
+
*
|
|
15
|
+
* Ordering/numeric operators (`>=`, `<=`, `>`, `<`) apply to `weight` only (the
|
|
16
|
+
* one numeric capability field); the string fields support only `=`/`==`/`!=`.
|
|
17
|
+
* A role with no `requires` (or an empty list) is open to any capability.
|
|
18
|
+
*
|
|
19
|
+
* Match semantics are FAIL-CLOSED for a gate: an absent field fails every
|
|
20
|
+
* positive predicate (`=`,`==`,`>=`,`<=`,`>`,`<`); only `!=` is satisfied by an
|
|
21
|
+
* absent field (the worker's field is provably not the forbidden value). A
|
|
22
|
+
* capability satisfies a role iff it satisfies EVERY predicate.
|
|
23
|
+
*/
|
|
24
|
+
import type { Capability } from "../protocol/index.ts";
|
|
25
|
+
/** The capability fields a `requires` predicate may gate on. */
|
|
26
|
+
export declare const REQUIRES_FIELDS: readonly ["cognition", "weight", "family", "host"];
|
|
27
|
+
export type RequiresField = (typeof REQUIRES_FIELDS)[number];
|
|
28
|
+
export type RequiresOp = "=" | "==" | "!=" | ">=" | "<=" | ">" | "<";
|
|
29
|
+
export interface RequiresPredicate {
|
|
30
|
+
readonly field: RequiresField;
|
|
31
|
+
readonly op: RequiresOp;
|
|
32
|
+
/** The compared value: a number for `weight`, a string otherwise. */
|
|
33
|
+
readonly value: string | number;
|
|
34
|
+
/** The original source text, for diagnostics. */
|
|
35
|
+
readonly source: string;
|
|
36
|
+
}
|
|
37
|
+
/** Raised when a `requires` entry is not a well-formed predicate. */
|
|
38
|
+
export declare class RequiresParseError extends Error {
|
|
39
|
+
readonly source: string;
|
|
40
|
+
constructor(source: string, detail: string);
|
|
41
|
+
}
|
|
42
|
+
/** Parse one `requires` entry into a predicate, or throw {@link RequiresParseError}. */
|
|
43
|
+
export declare function parseRequires(source: string): RequiresPredicate;
|
|
44
|
+
/** Parse every entry of a role's `requires` list. */
|
|
45
|
+
export declare function parseRequiresList(requires: readonly string[] | undefined): RequiresPredicate[];
|
|
46
|
+
/** True when `capability` satisfies a single predicate (fail-closed on absent fields). */
|
|
47
|
+
export declare function satisfiesPredicate(predicate: RequiresPredicate, capability: Capability): boolean;
|
|
48
|
+
/** True when `capability` satisfies EVERY predicate (an empty list is open). */
|
|
49
|
+
export declare function satisfiesRequires(predicates: readonly RequiresPredicate[], capability: Capability): boolean;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** The capability fields a `requires` predicate may gate on. */
|
|
2
|
+
export const REQUIRES_FIELDS = ["cognition", "weight", "family", "host"];
|
|
3
|
+
/** The numeric field; ordering operators are valid only for it. */
|
|
4
|
+
const NUMERIC_FIELD = "weight";
|
|
5
|
+
const ORDERING_OPS = new Set([">=", "<=", ">", "<"]);
|
|
6
|
+
const STRING_FIELDS = new Set(["cognition", "family", "host"]);
|
|
7
|
+
/** Raised when a `requires` entry is not a well-formed predicate. */
|
|
8
|
+
export class RequiresParseError extends Error {
|
|
9
|
+
source;
|
|
10
|
+
constructor(source, detail) {
|
|
11
|
+
super(`invalid requires predicate "${source}": ${detail}`);
|
|
12
|
+
this.name = "RequiresParseError";
|
|
13
|
+
this.source = source;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
// Longest operators first so `>=` is not mis-split as `>`; `==` before `=`.
|
|
17
|
+
const PREDICATE_RE = /^(cognition|weight|family|host)\s*(>=|<=|==|!=|=|>|<)\s*(.+?)\s*$/;
|
|
18
|
+
const FIELD_SET = new Set(REQUIRES_FIELDS);
|
|
19
|
+
function isRequiresField(value) {
|
|
20
|
+
return FIELD_SET.has(value);
|
|
21
|
+
}
|
|
22
|
+
function isRequiresOp(value) {
|
|
23
|
+
return value === "=" || value === "==" || value === "!=" || value === ">=" || value === "<=" || value === ">" || value === "<";
|
|
24
|
+
}
|
|
25
|
+
/** Parse one `requires` entry into a predicate, or throw {@link RequiresParseError}. */
|
|
26
|
+
export function parseRequires(source) {
|
|
27
|
+
const match = PREDICATE_RE.exec(source.trim());
|
|
28
|
+
if (match === null) {
|
|
29
|
+
throw new RequiresParseError(source, "expected `field op value` (field: cognition|weight|family|host)");
|
|
30
|
+
}
|
|
31
|
+
const field = match[1];
|
|
32
|
+
const op = match[2];
|
|
33
|
+
const rawValue = match[3];
|
|
34
|
+
if (!isRequiresField(field) || !isRequiresOp(op)) {
|
|
35
|
+
throw new RequiresParseError(source, "unrecognised field or operator");
|
|
36
|
+
}
|
|
37
|
+
if (field === NUMERIC_FIELD) {
|
|
38
|
+
const value = Number(rawValue);
|
|
39
|
+
if (!Number.isFinite(value)) {
|
|
40
|
+
throw new RequiresParseError(source, "weight predicate needs a finite numeric value");
|
|
41
|
+
}
|
|
42
|
+
return { field, op, value, source };
|
|
43
|
+
}
|
|
44
|
+
// String field: reject ordering operators (no total order on strings here).
|
|
45
|
+
if (ORDERING_OPS.has(op)) {
|
|
46
|
+
throw new RequiresParseError(source, `operator ${op} is only valid for the numeric field "weight"`);
|
|
47
|
+
}
|
|
48
|
+
if (!STRING_FIELDS.has(field)) {
|
|
49
|
+
throw new RequiresParseError(source, `field ${field} is not a string field`);
|
|
50
|
+
}
|
|
51
|
+
return { field, op, value: rawValue, source };
|
|
52
|
+
}
|
|
53
|
+
/** Parse every entry of a role's `requires` list. */
|
|
54
|
+
export function parseRequiresList(requires) {
|
|
55
|
+
if (requires === undefined)
|
|
56
|
+
return [];
|
|
57
|
+
return requires.map(parseRequires);
|
|
58
|
+
}
|
|
59
|
+
function evalNumeric(op, actual, expected) {
|
|
60
|
+
switch (op) {
|
|
61
|
+
case "=":
|
|
62
|
+
case "==":
|
|
63
|
+
return actual === expected;
|
|
64
|
+
case "!=":
|
|
65
|
+
return actual !== expected;
|
|
66
|
+
case ">=":
|
|
67
|
+
return actual >= expected;
|
|
68
|
+
case "<=":
|
|
69
|
+
return actual <= expected;
|
|
70
|
+
case ">":
|
|
71
|
+
return actual > expected;
|
|
72
|
+
case "<":
|
|
73
|
+
return actual < expected;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function evalString(op, actual, expected) {
|
|
77
|
+
switch (op) {
|
|
78
|
+
case "=":
|
|
79
|
+
case "==":
|
|
80
|
+
return actual === expected;
|
|
81
|
+
case "!=":
|
|
82
|
+
return actual !== expected;
|
|
83
|
+
// Ordering operators are rejected at parse time for string fields.
|
|
84
|
+
default:
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** True when `capability` satisfies a single predicate (fail-closed on absent fields). */
|
|
89
|
+
export function satisfiesPredicate(predicate, capability) {
|
|
90
|
+
const actual = capability[predicate.field];
|
|
91
|
+
if (actual === undefined) {
|
|
92
|
+
// An absent field can only satisfy a "must NOT equal" predicate.
|
|
93
|
+
return predicate.op === "!=";
|
|
94
|
+
}
|
|
95
|
+
if (predicate.field === NUMERIC_FIELD) {
|
|
96
|
+
if (typeof actual !== "number" || typeof predicate.value !== "number")
|
|
97
|
+
return false;
|
|
98
|
+
return evalNumeric(predicate.op, actual, predicate.value);
|
|
99
|
+
}
|
|
100
|
+
if (typeof actual !== "string" || typeof predicate.value !== "string")
|
|
101
|
+
return false;
|
|
102
|
+
return evalString(predicate.op, actual, predicate.value);
|
|
103
|
+
}
|
|
104
|
+
/** True when `capability` satisfies EVERY predicate (an empty list is open). */
|
|
105
|
+
export function satisfiesRequires(predicates, capability) {
|
|
106
|
+
return predicates.every((predicate) => satisfiesPredicate(predicate, capability));
|
|
107
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Capability, VocabDocument } from "../protocol/index.ts";
|
|
2
|
+
import { type RequiresPredicate } from "./requires.ts";
|
|
3
|
+
/** A role flattened out of the vocab tree, with its derived routing token. */
|
|
4
|
+
export interface ResolvedRole {
|
|
5
|
+
/** The leaf routing token (`network[.subnetwork…].role`, or a bare role). */
|
|
6
|
+
readonly token: string;
|
|
7
|
+
/** The network segment, absent for a bare role. */
|
|
8
|
+
readonly network?: string;
|
|
9
|
+
/** The subnetwork segments between network and role (possibly empty). */
|
|
10
|
+
readonly subnetworks: readonly string[];
|
|
11
|
+
/** The role segment. */
|
|
12
|
+
readonly role: string;
|
|
13
|
+
/** Cognition weight for the role, if declared. */
|
|
14
|
+
readonly weight?: number;
|
|
15
|
+
/** Normalised seats: a non-negative count, or the explicit named-seat list. */
|
|
16
|
+
readonly seats: number | readonly string[];
|
|
17
|
+
/** Diversity SLO opt-in: when true, seats must be filled by distinct families. */
|
|
18
|
+
readonly seatsDistinctFamily: boolean;
|
|
19
|
+
/** The parsed enrolment gate for the role. */
|
|
20
|
+
readonly requires: readonly RequiresPredicate[];
|
|
21
|
+
}
|
|
22
|
+
/** The result of resolving one capability against the vocab. */
|
|
23
|
+
export interface Resolution {
|
|
24
|
+
/** The SERVE token set — sorted, de-duplicated leaf tokens. */
|
|
25
|
+
readonly tokens: readonly string[];
|
|
26
|
+
/** The matched roles (sorted by token) the tokens came from. */
|
|
27
|
+
readonly roles: readonly ResolvedRole[];
|
|
28
|
+
}
|
|
29
|
+
/** Raised when the document handed to the resolver is not a valid vocab artifact. */
|
|
30
|
+
export declare class VocabDocumentError extends Error {
|
|
31
|
+
readonly errors: readonly {
|
|
32
|
+
path: string;
|
|
33
|
+
message: string;
|
|
34
|
+
}[];
|
|
35
|
+
constructor(errors: readonly {
|
|
36
|
+
path: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}[]);
|
|
39
|
+
}
|
|
40
|
+
export declare class VocabResolver {
|
|
41
|
+
#private;
|
|
42
|
+
/**
|
|
43
|
+
* Build a resolver over a vocab document. The document is re-validated against
|
|
44
|
+
* the S0 schema and every role's `requires` gate is parsed up front, so a
|
|
45
|
+
* malformed gate fails loudly at construction rather than silently at match.
|
|
46
|
+
*/
|
|
47
|
+
constructor(doc: VocabDocument);
|
|
48
|
+
/** The vocab artifact version this resolver was built from. */
|
|
49
|
+
get version(): number;
|
|
50
|
+
/** Every role in the vocab, sorted by token. */
|
|
51
|
+
roles(): readonly ResolvedRole[];
|
|
52
|
+
/** Every leaf token in the vocab, sorted. */
|
|
53
|
+
tokens(): readonly string[];
|
|
54
|
+
/** Look up a role by its routing token. */
|
|
55
|
+
roleForToken(token: string): ResolvedRole | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a declared enrolment capability to its SERVE token set: every role
|
|
58
|
+
* whose `requires` gate the capability satisfies. The token list is sorted and
|
|
59
|
+
* de-duplicated, so the same capability always yields the same SERVE.
|
|
60
|
+
*/
|
|
61
|
+
resolve(capability: Capability): Resolution;
|
|
62
|
+
}
|