agent-comms 3.6.0 → 3.7.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/core/capability-bubble-up.d.ts +69 -0
- package/dist/core/capability-bubble-up.d.ts.map +1 -0
- package/dist/core/capability-bubble-up.js +138 -0
- package/dist/core/capability-bubble-up.js.map +1 -0
- package/dist/test/capability-bubble-up.test.d.ts +2 -0
- package/dist/test/capability-bubble-up.test.d.ts.map +1 -0
- package/dist/test/capability-bubble-up.test.js +350 -0
- package/dist/test/capability-bubble-up.test.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ExaDev/agent-comms)
|
|
4
4
|
[](https://www.npmjs.com/package/agent-comms)
|
|
5
|
-
[](https://github.com/ExaDev/agent-comms/releases/tag/v3.7.0)
|
|
6
6
|
[](https://github.com/ExaDev/agent-comms/actions)
|
|
7
7
|
|
|
8
8
|
Cross-harness communication mesh for LLM agents: rooms, DMs, presence, and visibility over TCP with zero filesystem dependencies.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The routing policy for the ask tier's escalation path (agent-comms#164): walking a held capability token's own issuer chain when the immediate holder cannot grant a request itself. wire-mesh's capability-request primitive (domain/capability-request.ts) already carries the ask/held-open/decide/mint mechanics of a single hop; gossip-advertised grant candidates (domain/grant-candidates.ts) already answer "who nearer than the root can grant" for a requester picking its first hop. Neither decides what an intermediate device, having received a request it cannot fully satisfy from its own authority, should do next: that per-hop decision, and the handler that acts on it by minting a delegated child or forwarding the same ask further up the chain, is what this module adds.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities, split the same way capability-request.ts already splits its own: resolveBubbleUpRoute is the pure decision (grant from held depth, forward to the held token's own issuer, or no-route), and createBubbleUpCapabilityRequestHandler is the wire-facing orchestration that acts on it. Deliberately built directly on wire-mesh's exported mintCapabilityToken rather than reusing createCapabilityRequestHandler's own decide(): that primitive's accept path always mints a root token (no `parent`), which is correct for the primitive's own reference consumer (a room owner minting membership tokens directly) but cannot express "mint a narrower child of the token I already hold", the one thing granting from delegated depth actually requires.
|
|
5
|
+
*/
|
|
6
|
+
import type { MeshSession, IncomingManageRequest } from "wire-mesh-core/domain/mesh-session";
|
|
7
|
+
import { type RevocationCheck } from "wire-mesh-core/domain/tokens";
|
|
8
|
+
import { type CapabilityScope, type CapabilityToken, type DeviceId } from "wire-mesh-core/generated/protocol";
|
|
9
|
+
import type { Clock } from "wire-mesh-core/ports/clock";
|
|
10
|
+
import type { IdentityPort } from "wire-mesh-core/ports/identity";
|
|
11
|
+
export interface ResolveBubbleUpRouteOptions {
|
|
12
|
+
/** This device's own held credential establishing its authority over `capability`/`scope`, if it holds one at all. A device with none is either the capability's own structural root (which mints fresh from its own identity, outside this module's concern -- see wire-mesh's own createCapabilityRequestHandler for that path) or genuinely has nothing to grant or forward from. */
|
|
13
|
+
heldToken: CapabilityToken | undefined;
|
|
14
|
+
/** This device's own identity: both the crypto primitives heldToken is verified with, and (via `.deviceId`) the bearer heldToken must actually name. */
|
|
15
|
+
identity: IdentityPort;
|
|
16
|
+
clock: Clock;
|
|
17
|
+
revocation: RevocationCheck;
|
|
18
|
+
/** The capability and scope the incoming ask is for -- forwarded unchanged from the request this route decides for. */
|
|
19
|
+
capability: string;
|
|
20
|
+
scope: Readonly<CapabilityScope>;
|
|
21
|
+
/** The expiry and delegation depth a grant would carry if minted at this hop, checked against heldToken's own narrowing bounds via canGrant. The caller (see createBubbleUpCapabilityRequestHandler) owns this policy; this module only evaluates it. */
|
|
22
|
+
expires: number;
|
|
23
|
+
delegationsRemaining?: number;
|
|
24
|
+
}
|
|
25
|
+
export type BubbleUpRoute = {
|
|
26
|
+
kind: "grant";
|
|
27
|
+
token: CapabilityToken;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "forward";
|
|
30
|
+
target: DeviceId;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "no-route";
|
|
33
|
+
reason: "no-held-token" | "held-token-invalid" | "self-issued";
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Decides, for one device holding (or not holding) a capability token, whether it can grant a request for `capability`/`scope`/`expires`/`delegationsRemaining` from its own held depth, must forward the same ask to whoever issued its own token, or has no route to a grantor at all.
|
|
37
|
+
*
|
|
38
|
+
* heldToken is independently verified here (signature, expiry, revocation, and that this device is genuinely its bearer) via verifyCapabilityToken rather than trusted as already-checked: a caller's own held-token cache can go stale (expiry, a since-recorded revocation) between when it was stored and when this decision runs, and granting or forwarding on an invalid credential would be a real authority leak, not merely a stale-read symptom.
|
|
39
|
+
*
|
|
40
|
+
* Ordering matches the issue's own framing (agent-comms#164): a device with real remaining depth grants outright (checked via canGrant, the same narrowing arithmetic mintCapabilityToken itself enforces, so this decision and the mint it authorises can never disagree); otherwise, absent a nearer route this module has no way to discover on its own (see grant-candidates.ts for the requester's own gossip-based shortcut, deliberately a separate concern), the only further hop toward the root a token's own claims expose is its issuer. Forwarding to that issuer when it is this very device (a self-issued token, which canGrant already found could not satisfy the request) would loop forever, so that case reports no-route instead.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveBubbleUpRoute(options: Readonly<ResolveBubbleUpRouteOptions>): Promise<BubbleUpRoute>;
|
|
43
|
+
export interface CreateBubbleUpCapabilityRequestHandlerOptions {
|
|
44
|
+
/** The capability this handler grants or forwards asks for, checked against the incoming request's own `params.capability`. One handler is constructed per capability, the same convention capability-request.ts's own createCapabilityRequestHandler already establishes. */
|
|
45
|
+
capability: string;
|
|
46
|
+
identity: IdentityPort;
|
|
47
|
+
clock: Clock;
|
|
48
|
+
revocation: RevocationCheck;
|
|
49
|
+
/** The peer device-id authenticated on this session's own connection -- the requester, and so the bearer of whichever token this handler ultimately mints (whether granted from own depth or re-delegated from an upstream grant). */
|
|
50
|
+
bearerDevice: DeviceId;
|
|
51
|
+
/** The expiry and delegation depth this handler mints with, at either hop (its own grant, or its re-delegation of an upstream one). A fixed per-handler policy, not per-request: the wire-level capability-request carries no requested expiry or depth of its own (management.cddl leaves both to the granter), matching the same policy CapabilityGrantDecision.expires/delegationsRemaining already leaves to the domain in wire-mesh's base primitive. */
|
|
52
|
+
expires: number;
|
|
53
|
+
delegationsRemaining?: number;
|
|
54
|
+
/** Looks up this device's own currently held credential for `capability`, if any, at the moment each request is handled -- a function rather than a static value since a held token can be minted, renewed, or revoked over the handler's own lifetime. */
|
|
55
|
+
ownToken: () => CapabilityToken | undefined;
|
|
56
|
+
/** Used to forward an ask this device cannot grant itself further up the issuer chain. Only sendManageRequest is used, so a caller can supply a minimal stand-in in tests rather than a full MeshSession. */
|
|
57
|
+
session: Pick<MeshSession, "sendManageRequest">;
|
|
58
|
+
/** Bounds how long a forwarded ask waits for the next hop's own answer, forwarded directly to sendManageRequest's own identically-named parameter. Absent, a forward waits with no time limit of its own, exactly as sendManageRequest already behaves. */
|
|
59
|
+
forwardTimeoutMs?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Builds a reusable handler for one capability's incoming capability-requests that answers each one by walking the issuer chain: grant a delegated child from this device's own held depth when resolveBubbleUpRoute says it can, forward the identical ask to the held token's own issuer and re-delegate whatever grant comes back when it cannot, or refuse outright when there is no route to a grantor at all.
|
|
63
|
+
*
|
|
64
|
+
* Parses and validates the incoming request the same way capability-request.ts's own createCapabilityRequestHandler does (wrong capability or malformed payload refuses `{result:"error",code:"malformed"}`; an already-expired `valid-until` refuses `{result:"error",code:"expired"}`), so a requester using the ordinary requestCapability wrapper sees identical wire behaviour from either handler.
|
|
65
|
+
*
|
|
66
|
+
* A forward's own upstream outcome is relayed verbatim on refusal (the exact code and message an intermediate or root grantor sent), rather than collapsed to a generic denial, so the original requester learns exactly what stopped the chain. A forward's own granted token is independently re-verified (expectedBearer this device's own identity) before being trusted as a mint parent: mintCapabilityToken's own `parent` handling only checks structural narrowing against the decoded claims, not the token's signature, expiry, or revocation status, so an unverified inbound grant would otherwise let a malicious or buggy upstream peer hand this device a bogus credential to unknowingly re-delegate.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createBubbleUpCapabilityRequestHandler(options: Readonly<CreateBubbleUpCapabilityRequestHandlerOptions>): (incoming: Readonly<IncomingManageRequest>) => Promise<void>;
|
|
69
|
+
//# sourceMappingURL=capability-bubble-up.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-bubble-up.d.ts","sourceRoot":"","sources":["../../src/core/capability-bubble-up.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EACV,WAAW,EACX,qBAAqB,EACtB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,QAAQ,EACd,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAGlE,MAAM,WAAW,2BAA2B;IAC1C,wXAAwX;IACxX,SAAS,EAAE,eAAe,GAAG,SAAS,CAAC;IACvC,wJAAwJ;IACxJ,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,eAAe,CAAC;IAC5B,uHAAuH;IACvH,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACjC,yPAAyP;IACzP,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,GACrC;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,eAAe,GAAG,oBAAoB,GAAG,aAAa,CAAC;CAChE,CAAC;AAEN;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,QAAQ,CAAC,2BAA2B,CAAC,GAC7C,OAAO,CAAC,aAAa,CAAC,CAuCxB;AAED,MAAM,WAAW,6CAA6C;IAC5D,8QAA8Q;IAC9Q,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,KAAK,CAAC;IACb,UAAU,EAAE,eAAe,CAAC;IAC5B,sOAAsO;IACtO,YAAY,EAAE,QAAQ,CAAC;IACvB,8bAA8b;IAC9b,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,2PAA2P;IAC3P,QAAQ,EAAE,MAAM,eAAe,GAAG,SAAS,CAAC;IAC5C,6MAA6M;IAC7M,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAChD,2PAA2P;IAC3P,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,sCAAsC,CACpD,OAAO,EAAE,QAAQ,CAAC,6CAA6C,CAAC,GAC/D,CAAC,QAAQ,EAAE,QAAQ,CAAC,qBAAqB,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAgF9D"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The routing policy for the ask tier's escalation path (agent-comms#164): walking a held capability token's own issuer chain when the immediate holder cannot grant a request itself. wire-mesh's capability-request primitive (domain/capability-request.ts) already carries the ask/held-open/decide/mint mechanics of a single hop; gossip-advertised grant candidates (domain/grant-candidates.ts) already answer "who nearer than the root can grant" for a requester picking its first hop. Neither decides what an intermediate device, having received a request it cannot fully satisfy from its own authority, should do next: that per-hop decision, and the handler that acts on it by minting a delegated child or forwarding the same ask further up the chain, is what this module adds.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities, split the same way capability-request.ts already splits its own: resolveBubbleUpRoute is the pure decision (grant from held depth, forward to the held token's own issuer, or no-route), and createBubbleUpCapabilityRequestHandler is the wire-facing orchestration that acts on it. Deliberately built directly on wire-mesh's exported mintCapabilityToken rather than reusing createCapabilityRequestHandler's own decide(): that primitive's accept path always mints a root token (no `parent`), which is correct for the primitive's own reference consumer (a room owner minting membership tokens directly) but cannot express "mint a narrower child of the token I already hold", the one thing granting from delegated depth actually requires.
|
|
5
|
+
*/
|
|
6
|
+
import { buildCapabilityRequestCommand } from "wire-mesh-core/domain/capability-request";
|
|
7
|
+
import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
|
|
8
|
+
import { canGrant, mintCapabilityToken, verifyCapabilityToken, } from "wire-mesh-core/domain/tokens";
|
|
9
|
+
import { capabilityGrantOkSchema, capabilityRequestSchema, } from "wire-mesh-core/generated/protocol";
|
|
10
|
+
import { randomId } from "./random-id.js";
|
|
11
|
+
/**
|
|
12
|
+
* Decides, for one device holding (or not holding) a capability token, whether it can grant a request for `capability`/`scope`/`expires`/`delegationsRemaining` from its own held depth, must forward the same ask to whoever issued its own token, or has no route to a grantor at all.
|
|
13
|
+
*
|
|
14
|
+
* heldToken is independently verified here (signature, expiry, revocation, and that this device is genuinely its bearer) via verifyCapabilityToken rather than trusted as already-checked: a caller's own held-token cache can go stale (expiry, a since-recorded revocation) between when it was stored and when this decision runs, and granting or forwarding on an invalid credential would be a real authority leak, not merely a stale-read symptom.
|
|
15
|
+
*
|
|
16
|
+
* Ordering matches the issue's own framing (agent-comms#164): a device with real remaining depth grants outright (checked via canGrant, the same narrowing arithmetic mintCapabilityToken itself enforces, so this decision and the mint it authorises can never disagree); otherwise, absent a nearer route this module has no way to discover on its own (see grant-candidates.ts for the requester's own gossip-based shortcut, deliberately a separate concern), the only further hop toward the root a token's own claims expose is its issuer. Forwarding to that issuer when it is this very device (a self-issued token, which canGrant already found could not satisfy the request) would loop forever, so that case reports no-route instead.
|
|
17
|
+
*/
|
|
18
|
+
export async function resolveBubbleUpRoute(options) {
|
|
19
|
+
if (options.heldToken === undefined) {
|
|
20
|
+
return { kind: "no-route", reason: "no-held-token" };
|
|
21
|
+
}
|
|
22
|
+
const verdict = await verifyCapabilityToken(options.heldToken, {
|
|
23
|
+
identity: options.identity,
|
|
24
|
+
clock: options.clock,
|
|
25
|
+
revocation: options.revocation,
|
|
26
|
+
expectedBearer: options.identity.deviceId,
|
|
27
|
+
});
|
|
28
|
+
if (!verdict.ok) {
|
|
29
|
+
return { kind: "no-route", reason: "held-token-invalid" };
|
|
30
|
+
}
|
|
31
|
+
const canGrantDirectly = await canGrant(options.heldToken, options.identity.deviceId, {
|
|
32
|
+
capability: options.capability,
|
|
33
|
+
scope: options.scope,
|
|
34
|
+
expires: options.expires,
|
|
35
|
+
...(options.delegationsRemaining !== undefined
|
|
36
|
+
? { delegationsRemaining: options.delegationsRemaining }
|
|
37
|
+
: {}),
|
|
38
|
+
}, options.clock.now());
|
|
39
|
+
if (canGrantDirectly) {
|
|
40
|
+
return { kind: "grant", token: options.heldToken };
|
|
41
|
+
}
|
|
42
|
+
if (deviceIdToHex(verdict.claims.issuer) ===
|
|
43
|
+
deviceIdToHex(options.identity.deviceId)) {
|
|
44
|
+
return { kind: "no-route", reason: "self-issued" };
|
|
45
|
+
}
|
|
46
|
+
return { kind: "forward", target: verdict.claims.issuer };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds a reusable handler for one capability's incoming capability-requests that answers each one by walking the issuer chain: grant a delegated child from this device's own held depth when resolveBubbleUpRoute says it can, forward the identical ask to the held token's own issuer and re-delegate whatever grant comes back when it cannot, or refuse outright when there is no route to a grantor at all.
|
|
50
|
+
*
|
|
51
|
+
* Parses and validates the incoming request the same way capability-request.ts's own createCapabilityRequestHandler does (wrong capability or malformed payload refuses `{result:"error",code:"malformed"}`; an already-expired `valid-until` refuses `{result:"error",code:"expired"}`), so a requester using the ordinary requestCapability wrapper sees identical wire behaviour from either handler.
|
|
52
|
+
*
|
|
53
|
+
* A forward's own upstream outcome is relayed verbatim on refusal (the exact code and message an intermediate or root grantor sent), rather than collapsed to a generic denial, so the original requester learns exactly what stopped the chain. A forward's own granted token is independently re-verified (expectedBearer this device's own identity) before being trusted as a mint parent: mintCapabilityToken's own `parent` handling only checks structural narrowing against the decoded claims, not the token's signature, expiry, or revocation status, so an unverified inbound grant would otherwise let a malicious or buggy upstream peer hand this device a bogus credential to unknowingly re-delegate.
|
|
54
|
+
*/
|
|
55
|
+
export function createBubbleUpCapabilityRequestHandler(options) {
|
|
56
|
+
return async function handleBubbleUpCapabilityRequest(incoming) {
|
|
57
|
+
const parsed = capabilityRequestSchema.safeParse(incoming.command.params);
|
|
58
|
+
if (!parsed.success || parsed.data.capability !== options.capability) {
|
|
59
|
+
await incoming.respond({ result: "error", code: "malformed" });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const validUntil = parsed.data["valid-until"];
|
|
63
|
+
if (validUntil !== undefined && validUntil <= options.clock.now()) {
|
|
64
|
+
await incoming.respond({ result: "error", code: "expired" });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const route = await resolveBubbleUpRoute({
|
|
68
|
+
heldToken: options.ownToken(),
|
|
69
|
+
identity: options.identity,
|
|
70
|
+
clock: options.clock,
|
|
71
|
+
revocation: options.revocation,
|
|
72
|
+
capability: options.capability,
|
|
73
|
+
scope: incoming.scope,
|
|
74
|
+
expires: options.expires,
|
|
75
|
+
...(options.delegationsRemaining !== undefined
|
|
76
|
+
? { delegationsRemaining: options.delegationsRemaining }
|
|
77
|
+
: {}),
|
|
78
|
+
});
|
|
79
|
+
if (route.kind === "grant") {
|
|
80
|
+
await mintAndRespond(options, incoming, route.token);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (route.kind === "forward") {
|
|
84
|
+
const forwardOutcome = await options.session.sendManageRequest(buildCapabilityRequestCommand(options.capability, validUntil), incoming.scope, route.target, undefined, options.forwardTimeoutMs);
|
|
85
|
+
if (forwardOutcome.result !== "ok") {
|
|
86
|
+
await incoming.respond(forwardOutcome);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const parsedGrant = capabilityGrantOkSchema.safeParse(forwardOutcome);
|
|
90
|
+
if (!parsedGrant.success) {
|
|
91
|
+
await incoming.respond({ result: "error", code: "malformed" });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const upstreamVerdict = await verifyCapabilityToken(parsedGrant.data["granted-token"], {
|
|
95
|
+
identity: options.identity,
|
|
96
|
+
clock: options.clock,
|
|
97
|
+
revocation: options.revocation,
|
|
98
|
+
expectedBearer: options.identity.deviceId,
|
|
99
|
+
});
|
|
100
|
+
if (!upstreamVerdict.ok) {
|
|
101
|
+
await incoming.respond({
|
|
102
|
+
result: "error",
|
|
103
|
+
code: upstreamVerdict.reason,
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await mintAndRespond(options, incoming, parsedGrant.data["granted-token"]);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
await incoming.respond({
|
|
111
|
+
result: "error",
|
|
112
|
+
code: "denied",
|
|
113
|
+
message: `no route to a grantor for "${options.capability}"`,
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Mints a delegated child of `parent` (this device's own held token, or a freshly re-verified upstream grant) for the requester, and responds with it -- shared by both createBubbleUpCapabilityRequestHandler branches that end in a mint, so the two can never drift into two different ideas of what the resulting token should carry. */
|
|
118
|
+
async function mintAndRespond(options, incoming, parent) {
|
|
119
|
+
const verdict = await mintCapabilityToken({
|
|
120
|
+
identity: options.identity,
|
|
121
|
+
clock: options.clock,
|
|
122
|
+
tokenId: randomId(),
|
|
123
|
+
bearer: options.bearerDevice,
|
|
124
|
+
capability: options.capability,
|
|
125
|
+
scope: incoming.scope,
|
|
126
|
+
expires: options.expires,
|
|
127
|
+
...(options.delegationsRemaining !== undefined
|
|
128
|
+
? { delegationsRemaining: options.delegationsRemaining }
|
|
129
|
+
: {}),
|
|
130
|
+
parent,
|
|
131
|
+
});
|
|
132
|
+
if (!verdict.ok) {
|
|
133
|
+
await incoming.respond({ result: "error", code: "mint_failed" });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await incoming.respond({ result: "ok", "granted-token": verdict.token });
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=capability-bubble-up.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-bubble-up.js","sourceRoot":"","sources":["../../src/core/capability-bubble-up.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-bubble-up.test.d.ts","sourceRoot":"","sources":["../../src/test/capability-bubble-up.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { webcrypto } from "node:crypto";
|
|
2
|
+
import { describe, it, expect, vi } from "vitest";
|
|
3
|
+
import { createNodeIdentity } from "wire-mesh-core/adapters/node-identity";
|
|
4
|
+
import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
|
|
5
|
+
import { mintCapabilityToken } from "wire-mesh-core/domain/tokens";
|
|
6
|
+
import { createRevocationView } from "wire-mesh-core/domain/revocation-view";
|
|
7
|
+
import { verifyCapabilityToken } from "wire-mesh-core/domain/tokens";
|
|
8
|
+
import { resolveBubbleUpRoute, createBubbleUpCapabilityRequestHandler, } from "../core/capability-bubble-up.js";
|
|
9
|
+
const ES256 = -7;
|
|
10
|
+
const HOUR_MS = 3_600_000;
|
|
11
|
+
const NOW_MS = 1_893_456_000_000;
|
|
12
|
+
const EXPIRES_MS = NOW_MS + HOUR_MS;
|
|
13
|
+
const CAPABILITY = "exec:pty";
|
|
14
|
+
const SCOPE = { kind: "folder" };
|
|
15
|
+
function buf(bytes) {
|
|
16
|
+
return Uint8Array.from(bytes);
|
|
17
|
+
}
|
|
18
|
+
async function generateEs256Identity() {
|
|
19
|
+
const keyPair = await webcrypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
|
|
20
|
+
const publicKeyBytes = new Uint8Array(await webcrypto.subtle.exportKey("raw", keyPair.publicKey));
|
|
21
|
+
return createNodeIdentity(keyPair.privateKey, publicKeyBytes, ES256);
|
|
22
|
+
}
|
|
23
|
+
function fixedClock(atMs) {
|
|
24
|
+
return { now: () => atMs };
|
|
25
|
+
}
|
|
26
|
+
let issuedTokenIds = 0;
|
|
27
|
+
function nextTokenId() {
|
|
28
|
+
issuedTokenIds += 1;
|
|
29
|
+
return buf([issuedTokenIds]);
|
|
30
|
+
}
|
|
31
|
+
async function mint(issuer, bearer, options = {}) {
|
|
32
|
+
const verdict = await mintCapabilityToken({
|
|
33
|
+
identity: issuer,
|
|
34
|
+
clock: fixedClock(options.mintedAt ?? NOW_MS),
|
|
35
|
+
tokenId: nextTokenId(),
|
|
36
|
+
bearer: bearer.deviceId,
|
|
37
|
+
capability: options.capability ?? CAPABILITY,
|
|
38
|
+
scope: options.scope ?? SCOPE,
|
|
39
|
+
expires: options.expires ?? EXPIRES_MS,
|
|
40
|
+
...(options.delegationsRemaining !== undefined
|
|
41
|
+
? { delegationsRemaining: options.delegationsRemaining }
|
|
42
|
+
: {}),
|
|
43
|
+
...(options.parent !== undefined ? { parent: options.parent } : {}),
|
|
44
|
+
});
|
|
45
|
+
if (!verdict.ok)
|
|
46
|
+
throw new Error(`mint failed: ${verdict.reason}`);
|
|
47
|
+
return verdict.token;
|
|
48
|
+
}
|
|
49
|
+
describe("resolveBubbleUpRoute", () => {
|
|
50
|
+
it("grants directly when the held token has depth remaining that narrows the request", async () => {
|
|
51
|
+
const root = await generateEs256Identity();
|
|
52
|
+
const holder = await generateEs256Identity();
|
|
53
|
+
const heldToken = await mint(root, holder, { delegationsRemaining: 2 });
|
|
54
|
+
const route = await resolveBubbleUpRoute({
|
|
55
|
+
heldToken,
|
|
56
|
+
identity: holder,
|
|
57
|
+
clock: fixedClock(NOW_MS),
|
|
58
|
+
revocation: createRevocationView(),
|
|
59
|
+
capability: CAPABILITY,
|
|
60
|
+
scope: SCOPE,
|
|
61
|
+
expires: EXPIRES_MS,
|
|
62
|
+
delegationsRemaining: 1,
|
|
63
|
+
});
|
|
64
|
+
expect(route.kind).toBe("grant");
|
|
65
|
+
if (route.kind === "grant") {
|
|
66
|
+
expect(route.token).toBe(heldToken);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
it("forwards to the held token's own issuer when its delegation depth is exhausted", async () => {
|
|
70
|
+
const root = await generateEs256Identity();
|
|
71
|
+
const holder = await generateEs256Identity();
|
|
72
|
+
const heldToken = await mint(root, holder, { delegationsRemaining: 0 });
|
|
73
|
+
const route = await resolveBubbleUpRoute({
|
|
74
|
+
heldToken,
|
|
75
|
+
identity: holder,
|
|
76
|
+
clock: fixedClock(NOW_MS),
|
|
77
|
+
revocation: createRevocationView(),
|
|
78
|
+
capability: CAPABILITY,
|
|
79
|
+
scope: SCOPE,
|
|
80
|
+
expires: EXPIRES_MS,
|
|
81
|
+
delegationsRemaining: 0,
|
|
82
|
+
});
|
|
83
|
+
expect(route.kind).toBe("forward");
|
|
84
|
+
if (route.kind === "forward") {
|
|
85
|
+
expect(deviceIdToHex(route.target)).toBe(deviceIdToHex(root.deviceId));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
it("forwards to the held token's own issuer when the requested scope does not narrow", async () => {
|
|
89
|
+
const root = await generateEs256Identity();
|
|
90
|
+
const holder = await generateEs256Identity();
|
|
91
|
+
const heldToken = await mint(root, holder, {
|
|
92
|
+
scope: { kind: "room", path: "abc/general" },
|
|
93
|
+
delegationsRemaining: 3,
|
|
94
|
+
});
|
|
95
|
+
const route = await resolveBubbleUpRoute({
|
|
96
|
+
heldToken,
|
|
97
|
+
identity: holder,
|
|
98
|
+
clock: fixedClock(NOW_MS),
|
|
99
|
+
revocation: createRevocationView(),
|
|
100
|
+
capability: CAPABILITY,
|
|
101
|
+
scope: { kind: "room", path: "abc/other" },
|
|
102
|
+
expires: EXPIRES_MS,
|
|
103
|
+
delegationsRemaining: 1,
|
|
104
|
+
});
|
|
105
|
+
expect(route.kind).toBe("forward");
|
|
106
|
+
if (route.kind === "forward") {
|
|
107
|
+
expect(deviceIdToHex(route.target)).toBe(deviceIdToHex(root.deviceId));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
it("reports no-route when this device holds no token for the capability at all", async () => {
|
|
111
|
+
const holder = await generateEs256Identity();
|
|
112
|
+
const route = await resolveBubbleUpRoute({
|
|
113
|
+
heldToken: undefined,
|
|
114
|
+
identity: holder,
|
|
115
|
+
clock: fixedClock(NOW_MS),
|
|
116
|
+
revocation: createRevocationView(),
|
|
117
|
+
capability: CAPABILITY,
|
|
118
|
+
scope: SCOPE,
|
|
119
|
+
expires: EXPIRES_MS,
|
|
120
|
+
});
|
|
121
|
+
expect(route).toEqual({ kind: "no-route", reason: "no-held-token" });
|
|
122
|
+
});
|
|
123
|
+
it("reports no-route when the held token has already expired", async () => {
|
|
124
|
+
const root = await generateEs256Identity();
|
|
125
|
+
const holder = await generateEs256Identity();
|
|
126
|
+
const heldToken = await mint(root, holder, {
|
|
127
|
+
mintedAt: NOW_MS - HOUR_MS,
|
|
128
|
+
expires: NOW_MS - 1,
|
|
129
|
+
delegationsRemaining: 2,
|
|
130
|
+
});
|
|
131
|
+
const route = await resolveBubbleUpRoute({
|
|
132
|
+
heldToken,
|
|
133
|
+
identity: holder,
|
|
134
|
+
clock: fixedClock(NOW_MS),
|
|
135
|
+
revocation: createRevocationView(),
|
|
136
|
+
capability: CAPABILITY,
|
|
137
|
+
scope: SCOPE,
|
|
138
|
+
expires: NOW_MS,
|
|
139
|
+
delegationsRemaining: 1,
|
|
140
|
+
});
|
|
141
|
+
expect(route).toEqual({ kind: "no-route", reason: "held-token-invalid" });
|
|
142
|
+
});
|
|
143
|
+
it("reports no-route rather than forwarding to itself when its held token is self-issued", async () => {
|
|
144
|
+
const holder = await generateEs256Identity();
|
|
145
|
+
const heldToken = await mint(holder, holder, { delegationsRemaining: 0 });
|
|
146
|
+
const route = await resolveBubbleUpRoute({
|
|
147
|
+
heldToken,
|
|
148
|
+
identity: holder,
|
|
149
|
+
clock: fixedClock(NOW_MS),
|
|
150
|
+
revocation: createRevocationView(),
|
|
151
|
+
capability: CAPABILITY,
|
|
152
|
+
scope: SCOPE,
|
|
153
|
+
expires: EXPIRES_MS,
|
|
154
|
+
delegationsRemaining: 0,
|
|
155
|
+
});
|
|
156
|
+
expect(route).toEqual({ kind: "no-route", reason: "self-issued" });
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
let nextRequestId = 0;
|
|
160
|
+
function fakeIncoming(params) {
|
|
161
|
+
nextRequestId += 1;
|
|
162
|
+
return {
|
|
163
|
+
requestId: nextRequestId,
|
|
164
|
+
command: { verb: CAPABILITY, params },
|
|
165
|
+
scope: SCOPE,
|
|
166
|
+
respond: vi.fn(async () => undefined),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
describe("createBubbleUpCapabilityRequestHandler", () => {
|
|
170
|
+
it("mints a delegated child token when this device can grant from its own held depth", async () => {
|
|
171
|
+
const root = await generateEs256Identity();
|
|
172
|
+
const holder = await generateEs256Identity();
|
|
173
|
+
const requester = await generateEs256Identity();
|
|
174
|
+
const heldToken = await mint(root, holder, { delegationsRemaining: 2 });
|
|
175
|
+
const sendManageRequest = vi.fn();
|
|
176
|
+
const handler = createBubbleUpCapabilityRequestHandler({
|
|
177
|
+
capability: CAPABILITY,
|
|
178
|
+
identity: holder,
|
|
179
|
+
clock: fixedClock(NOW_MS),
|
|
180
|
+
revocation: createRevocationView(),
|
|
181
|
+
bearerDevice: requester.deviceId,
|
|
182
|
+
expires: EXPIRES_MS,
|
|
183
|
+
delegationsRemaining: 1,
|
|
184
|
+
ownToken: () => heldToken,
|
|
185
|
+
session: { sendManageRequest },
|
|
186
|
+
});
|
|
187
|
+
const incoming = fakeIncoming({
|
|
188
|
+
verb: "capability.request",
|
|
189
|
+
capability: CAPABILITY,
|
|
190
|
+
});
|
|
191
|
+
await handler(incoming);
|
|
192
|
+
expect(sendManageRequest).not.toHaveBeenCalled();
|
|
193
|
+
expect(incoming.respond).toHaveBeenCalledTimes(1);
|
|
194
|
+
const outcome = incoming.respond.mock.calls[0]?.[0];
|
|
195
|
+
expect(outcome.result).toBe("ok");
|
|
196
|
+
const verdict = await verifyCapabilityToken(outcome["granted-token"], {
|
|
197
|
+
identity: holder,
|
|
198
|
+
clock: fixedClock(NOW_MS),
|
|
199
|
+
revocation: createRevocationView(),
|
|
200
|
+
expectedBearer: requester.deviceId,
|
|
201
|
+
});
|
|
202
|
+
expect(verdict.ok).toBe(true);
|
|
203
|
+
if (verdict.ok) {
|
|
204
|
+
expect(deviceIdToHex(verdict.rootIssuer)).toBe(deviceIdToHex(root.deviceId));
|
|
205
|
+
expect(verdict.depth).toBe(1);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
it("responds malformed for a request naming a different capability", async () => {
|
|
209
|
+
const holder = await generateEs256Identity();
|
|
210
|
+
const requester = await generateEs256Identity();
|
|
211
|
+
const handler = createBubbleUpCapabilityRequestHandler({
|
|
212
|
+
capability: CAPABILITY,
|
|
213
|
+
identity: holder,
|
|
214
|
+
clock: fixedClock(NOW_MS),
|
|
215
|
+
revocation: createRevocationView(),
|
|
216
|
+
bearerDevice: requester.deviceId,
|
|
217
|
+
expires: EXPIRES_MS,
|
|
218
|
+
ownToken: () => undefined,
|
|
219
|
+
session: { sendManageRequest: vi.fn() },
|
|
220
|
+
});
|
|
221
|
+
const incoming = fakeIncoming({
|
|
222
|
+
verb: "capability.request",
|
|
223
|
+
capability: "room:member",
|
|
224
|
+
});
|
|
225
|
+
await handler(incoming);
|
|
226
|
+
expect(incoming.respond).toHaveBeenCalledWith({
|
|
227
|
+
result: "error",
|
|
228
|
+
code: "malformed",
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
it("responds expired for an already-stale ask", async () => {
|
|
232
|
+
const holder = await generateEs256Identity();
|
|
233
|
+
const requester = await generateEs256Identity();
|
|
234
|
+
const handler = createBubbleUpCapabilityRequestHandler({
|
|
235
|
+
capability: CAPABILITY,
|
|
236
|
+
identity: holder,
|
|
237
|
+
clock: fixedClock(NOW_MS),
|
|
238
|
+
revocation: createRevocationView(),
|
|
239
|
+
bearerDevice: requester.deviceId,
|
|
240
|
+
expires: EXPIRES_MS,
|
|
241
|
+
ownToken: () => undefined,
|
|
242
|
+
session: { sendManageRequest: vi.fn() },
|
|
243
|
+
});
|
|
244
|
+
const incoming = fakeIncoming({
|
|
245
|
+
verb: "capability.request",
|
|
246
|
+
capability: CAPABILITY,
|
|
247
|
+
"valid-until": NOW_MS - 1,
|
|
248
|
+
});
|
|
249
|
+
await handler(incoming);
|
|
250
|
+
expect(incoming.respond).toHaveBeenCalledWith({
|
|
251
|
+
result: "error",
|
|
252
|
+
code: "expired",
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
it("forwards to the held token's own issuer and re-delegates the returned grant when its own depth is exhausted", async () => {
|
|
256
|
+
const root = await generateEs256Identity();
|
|
257
|
+
const holder = await generateEs256Identity();
|
|
258
|
+
const requester = await generateEs256Identity();
|
|
259
|
+
const heldToken = await mint(root, holder, { delegationsRemaining: 0 });
|
|
260
|
+
const upstreamGrant = await mint(root, holder, { delegationsRemaining: 1 });
|
|
261
|
+
const sendManageRequest = vi.fn(async () => ({
|
|
262
|
+
result: "ok",
|
|
263
|
+
"granted-token": upstreamGrant,
|
|
264
|
+
}));
|
|
265
|
+
const handler = createBubbleUpCapabilityRequestHandler({
|
|
266
|
+
capability: CAPABILITY,
|
|
267
|
+
identity: holder,
|
|
268
|
+
clock: fixedClock(NOW_MS),
|
|
269
|
+
revocation: createRevocationView(),
|
|
270
|
+
bearerDevice: requester.deviceId,
|
|
271
|
+
expires: EXPIRES_MS,
|
|
272
|
+
delegationsRemaining: 0,
|
|
273
|
+
ownToken: () => heldToken,
|
|
274
|
+
session: { sendManageRequest },
|
|
275
|
+
});
|
|
276
|
+
const incoming = fakeIncoming({
|
|
277
|
+
verb: "capability.request",
|
|
278
|
+
capability: CAPABILITY,
|
|
279
|
+
});
|
|
280
|
+
await handler(incoming);
|
|
281
|
+
expect(sendManageRequest).toHaveBeenCalledWith(expect.objectContaining({ verb: CAPABILITY }), SCOPE, root.deviceId, undefined, undefined);
|
|
282
|
+
const outcome = incoming.respond.mock.calls[0]?.[0];
|
|
283
|
+
expect(outcome.result).toBe("ok");
|
|
284
|
+
const verdict = await verifyCapabilityToken(outcome["granted-token"], {
|
|
285
|
+
identity: holder,
|
|
286
|
+
clock: fixedClock(NOW_MS),
|
|
287
|
+
revocation: createRevocationView(),
|
|
288
|
+
expectedBearer: requester.deviceId,
|
|
289
|
+
});
|
|
290
|
+
expect(verdict.ok).toBe(true);
|
|
291
|
+
if (verdict.ok) {
|
|
292
|
+
expect(deviceIdToHex(verdict.rootIssuer)).toBe(deviceIdToHex(root.deviceId));
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
it("relays the upstream denial unchanged when forwarding is refused", async () => {
|
|
296
|
+
const root = await generateEs256Identity();
|
|
297
|
+
const holder = await generateEs256Identity();
|
|
298
|
+
const requester = await generateEs256Identity();
|
|
299
|
+
const heldToken = await mint(root, holder, { delegationsRemaining: 0 });
|
|
300
|
+
const sendManageRequest = vi.fn(async () => ({
|
|
301
|
+
result: "error",
|
|
302
|
+
code: "denied",
|
|
303
|
+
message: "no more depth to give",
|
|
304
|
+
}));
|
|
305
|
+
const handler = createBubbleUpCapabilityRequestHandler({
|
|
306
|
+
capability: CAPABILITY,
|
|
307
|
+
identity: holder,
|
|
308
|
+
clock: fixedClock(NOW_MS),
|
|
309
|
+
revocation: createRevocationView(),
|
|
310
|
+
bearerDevice: requester.deviceId,
|
|
311
|
+
expires: EXPIRES_MS,
|
|
312
|
+
delegationsRemaining: 0,
|
|
313
|
+
ownToken: () => heldToken,
|
|
314
|
+
session: { sendManageRequest },
|
|
315
|
+
});
|
|
316
|
+
const incoming = fakeIncoming({
|
|
317
|
+
verb: "capability.request",
|
|
318
|
+
capability: CAPABILITY,
|
|
319
|
+
});
|
|
320
|
+
await handler(incoming);
|
|
321
|
+
expect(incoming.respond).toHaveBeenCalledWith({
|
|
322
|
+
result: "error",
|
|
323
|
+
code: "denied",
|
|
324
|
+
message: "no more depth to give",
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
it("responds denied when this device holds no token and has nothing to forward from", async () => {
|
|
328
|
+
const holder = await generateEs256Identity();
|
|
329
|
+
const requester = await generateEs256Identity();
|
|
330
|
+
const handler = createBubbleUpCapabilityRequestHandler({
|
|
331
|
+
capability: CAPABILITY,
|
|
332
|
+
identity: holder,
|
|
333
|
+
clock: fixedClock(NOW_MS),
|
|
334
|
+
revocation: createRevocationView(),
|
|
335
|
+
bearerDevice: requester.deviceId,
|
|
336
|
+
expires: EXPIRES_MS,
|
|
337
|
+
ownToken: () => undefined,
|
|
338
|
+
session: { sendManageRequest: vi.fn() },
|
|
339
|
+
});
|
|
340
|
+
const incoming = fakeIncoming({
|
|
341
|
+
verb: "capability.request",
|
|
342
|
+
capability: CAPABILITY,
|
|
343
|
+
});
|
|
344
|
+
await handler(incoming);
|
|
345
|
+
const outcome = incoming.respond.mock.calls[0]?.[0];
|
|
346
|
+
expect(outcome.result).toBe("error");
|
|
347
|
+
expect(outcome.code).toBe("denied");
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
//# sourceMappingURL=capability-bubble-up.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-bubble-up.test.js","sourceRoot":"","sources":["../../src/test/capability-bubble-up.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-comms",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"description": "Cross-harness communication mesh for LLM agents — rooms, DMs, presence, and real-time push delivery over TCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
|