@hediet/linkrpc 0.0.1
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 +383 -0
- package/dist/chunks/_empty-crypto-Bi0tGx5K.js +8 -0
- package/dist/chunks/boundedTrafficSubscription-1L592xc7.js +1126 -0
- package/dist/chunks/boundedTrafficSubscription-1L592xc7.js.map +1 -0
- package/dist/chunks/hub.interfaces-BzWfsVT2.js +526 -0
- package/dist/chunks/hub.interfaces-BzWfsVT2.js.map +1 -0
- package/dist/chunks/hubAccess-DwTZPiI8.d.ts +79 -0
- package/dist/chunks/hubAccess-DwTZPiI8.d.ts.map +1 -0
- package/dist/chunks/hubFacade-CQflVkVC.js +85 -0
- package/dist/chunks/hubFacade-CQflVkVC.js.map +1 -0
- package/dist/chunks/hubFacade-Dkgw2pTi.d.ts +101 -0
- package/dist/chunks/hubFacade-Dkgw2pTi.d.ts.map +1 -0
- package/dist/chunks/linkRpcConnection-CtlQmetO.d.ts +3623 -0
- package/dist/chunks/linkRpcConnection-CtlQmetO.d.ts.map +1 -0
- package/dist/chunks/rolldown-runtime-4LSo1kEK.js +17 -0
- package/dist/chunks/src-D3NUIwyo.js +7795 -0
- package/dist/chunks/src-D3NUIwyo.js.map +1 -0
- package/dist/hub/client/index.d.ts +50 -0
- package/dist/hub/client/index.d.ts.map +1 -0
- package/dist/hub/client/index.js +97 -0
- package/dist/hub/client/index.js.map +1 -0
- package/dist/hub/common/index.d.ts +1189 -0
- package/dist/hub/common/index.d.ts.map +1 -0
- package/dist/hub/common/index.js +267 -0
- package/dist/hub/common/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/inspection/index.d.ts +66 -0
- package/dist/inspection/index.d.ts.map +1 -0
- package/dist/inspection/index.js +6 -0
- package/dist/node.d.ts +548 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +1087 -0
- package/dist/node.js.map +1 -0
- package/dist/web.d.ts +44 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +58 -0
- package/dist/web.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,1189 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
import { Bi as RequestType, Br as TopologyTransportInfo, Gt as Channel, Jt as IRequestSender, K as Principal, L as CapProvider, Ti as InterfaceDefinition, V as SigningCallCtx, Yi as MethodSchema, an as IMessageTransport, d as ServiceIdPattern, qi as LinkRpcInterfaceSchema, u as RootPrincipalSet, wi as InterfaceClient, z as ManagedSigningChannel } from "../../chunks/linkRpcConnection-CtlQmetO.js";
|
|
6
|
+
import { a as HubAccessResult, i as HubAccessRequest, n as HubAccessPattern, o as findCoveringCapabilities, r as HubAccessPermission, t as HubAccessDuration } from "../../chunks/hubAccess-DwTZPiI8.js";
|
|
7
|
+
//#region src/hub/common/serviceId.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* A **ServiceId** is a `'/'`-segmented address naming a routing destination
|
|
10
|
+
* on a {@link Hub}. The empty string is the **root** (the hub's own / local
|
|
11
|
+
* services); every other id is one or more non-empty segments joined by
|
|
12
|
+
* `'/'`.
|
|
13
|
+
*
|
|
14
|
+
* Examples:
|
|
15
|
+
* - `""` → root (valid)
|
|
16
|
+
* - `"foo"` → valid
|
|
17
|
+
* - `"foo/bar"` → valid
|
|
18
|
+
* - `"/"` → invalid (empty segments)
|
|
19
|
+
* - `"/foo"` → invalid (leading separator)
|
|
20
|
+
* - `"foo/"` → invalid (trailing separator)
|
|
21
|
+
* - `"foo//bar"` → invalid (empty interior segment)
|
|
22
|
+
*
|
|
23
|
+
* It is a plain `string` alias — the name exists only to mark the role at
|
|
24
|
+
* call sites. Use {@link isValidServiceId} / {@link splitServiceId} to work
|
|
25
|
+
* with it structurally.
|
|
26
|
+
*/
|
|
27
|
+
type ServiceId = string;
|
|
28
|
+
/** The root service id — the hub's own local services. */
|
|
29
|
+
declare const ROOT_SERVICE_ID: ServiceId;
|
|
30
|
+
/** The separator between service id segments. */
|
|
31
|
+
declare const SERVICE_ID_SEPARATOR = "/";
|
|
32
|
+
/**
|
|
33
|
+
* Whether `id` is a well-formed {@link ServiceId}. The root (`""`) is
|
|
34
|
+
* valid; any other value must be non-empty segments joined by single
|
|
35
|
+
* `'/'`s, with no leading, trailing, or empty segments.
|
|
36
|
+
*/
|
|
37
|
+
declare function isValidServiceId(id: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Split a {@link ServiceId} into its segments. The root (`""`) yields the
|
|
40
|
+
* empty array; `"foo/bar"` yields `["foo", "bar"]`.
|
|
41
|
+
*
|
|
42
|
+
* Does not validate — pair with {@link isValidServiceId} when the input is
|
|
43
|
+
* untrusted.
|
|
44
|
+
*/
|
|
45
|
+
declare function splitServiceId(id: ServiceId): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Whether `serviceId` is equal to or nested beneath `prefix` (segment-aware):
|
|
48
|
+
* `"a/b"` covers `"a/b"` and `"a/b/c"` but **not** `"a/bc"`. The root prefix
|
|
49
|
+
* (`""`) covers everything.
|
|
50
|
+
*/
|
|
51
|
+
declare function isServiceIdUnder(serviceId: ServiceId, prefix: ServiceId): boolean;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/hub/common/transportServer.d.ts
|
|
54
|
+
/**
|
|
55
|
+
* A link the hub can route, plus a close signal. {@link IMessageTransport}
|
|
56
|
+
* on its own has no "closed" event (it only learns of teardown through its
|
|
57
|
+
* own `dispose`), but a *server*-produced connection must be able to tell
|
|
58
|
+
* downstream consumers when the remote end goes away so overlays can be torn
|
|
59
|
+
* down and prefixes released. {@link Transport} adds exactly that.
|
|
60
|
+
*/
|
|
61
|
+
interface Transport extends IMessageTransport {
|
|
62
|
+
/** Optional diagnostics surfaced on the corresponding topology link. Never include credentials or payloads. */
|
|
63
|
+
readonly topologyInfo?: TopologyTransportInfo;
|
|
64
|
+
/**
|
|
65
|
+
* Register a handler fired exactly once when the transport closes (remote
|
|
66
|
+
* hang-up or local {@link IMessageTransport.dispose}). Handlers added
|
|
67
|
+
* after close fire on the next microtask.
|
|
68
|
+
*/
|
|
69
|
+
onDidClose(handler: () => void): void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A source of inbound {@link Transport}s — the single seam every transport
|
|
73
|
+
* backend (UDS socket, websocket, iframe postMessage, in-process pair)
|
|
74
|
+
* implements. The hub side consumes only this; it never names `net.Socket`
|
|
75
|
+
* or any backend-specific type.
|
|
76
|
+
*/
|
|
77
|
+
interface ITransportServer<T extends Transport> {
|
|
78
|
+
/**
|
|
79
|
+
* Install the handler invoked once per accepted transport. Replacing the
|
|
80
|
+
* handler is allowed; only the most recent one receives new connections.
|
|
81
|
+
*/
|
|
82
|
+
setConnectionHandler(handler: (transport: T) => void): void;
|
|
83
|
+
dispose(): void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Lift a transport server from emitting `T1` to emitting `T2`. The `map` may
|
|
87
|
+
* be async (e.g. to attest a peer before exposing it) and may return
|
|
88
|
+
* `undefined` to **drop** a connection — the downstream handler is simply not
|
|
89
|
+
* invoked for it. The transport's identity is preserved when the map returns
|
|
90
|
+
* the same object (important: the hub keys routing state on the transport
|
|
91
|
+
* reference), so prefer annotating in place over wrapping.
|
|
92
|
+
*/
|
|
93
|
+
declare function mapTransport<T1 extends Transport, T2 extends Transport>(source: ITransportServer<T1>, map: (transport: T1) => T2 | undefined | Promise<T2 | undefined>): ITransportServer<T2>;
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/hub/common/hub.interfaces.d.ts
|
|
96
|
+
/**
|
|
97
|
+
* `hubServiceIdRegistry::registerServiceId` — a participant claims a prefix
|
|
98
|
+
* **outside** its provenance-granted namespace.
|
|
99
|
+
*
|
|
100
|
+
* Reached under the hub's own service id (form-3
|
|
101
|
+
* `<hubServiceId>::hubServiceIdRegistry::registerServiceId`) and gated by an
|
|
102
|
+
* admin-rooted capability. For claims **within** the connection's granted
|
|
103
|
+
* namespace, use the cheaper, capability-free `hubGrantedServiceId::register`
|
|
104
|
+
* instead.
|
|
105
|
+
*/
|
|
106
|
+
declare const hubServiceIdRegistryInterface: InterfaceDefinition<{
|
|
107
|
+
registerServiceId: RequestType<{
|
|
108
|
+
requestedPrefix: string;
|
|
109
|
+
}, Record<string, never>, any, never, never>;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* `hubGrantedServiceId` — the ungated connection surface, served at the
|
|
113
|
+
* connection root on each participant's overlay (so it is reachable directly,
|
|
114
|
+
* never forwarded, and needs no capability of its own). Two jobs:
|
|
115
|
+
*
|
|
116
|
+
* - `get` (unsigned) reports the topology facts the hub decided for this
|
|
117
|
+
* connection — where it is and what it may claim.
|
|
118
|
+
* - `register` (provenance-gated) claims a prefix **within**
|
|
119
|
+
* this connection's granted namespace — the capability-free claim path.
|
|
120
|
+
* Claims outside the namespace go through the admin-gated
|
|
121
|
+
* `<hubServiceId>::hubServiceIdRegistry::registerServiceId` door instead.
|
|
122
|
+
* - `getHubServiceId` (unsigned) reports the serviceId prefix the hub mounts
|
|
123
|
+
* its own global services under — the prefix to address the admin-gated
|
|
124
|
+
* registry and reflection endpoints through. Served at the connection root
|
|
125
|
+
* so a participant can discover it **before** it knows where the hub lives.
|
|
126
|
+
*
|
|
127
|
+
* The hub's consent front door (`hubAccess::*`) is served at the connection
|
|
128
|
+
* root too, so it needs no bootstrap capability and is reached directly.
|
|
129
|
+
*/
|
|
130
|
+
declare const hubGrantedServiceIdInterface: InterfaceDefinition<{
|
|
131
|
+
/**
|
|
132
|
+
* Connection facts (unsigned). The participant pulls, on each
|
|
133
|
+
* (re)connect, the topology facts the hub has decided for this
|
|
134
|
+
* connection:
|
|
135
|
+
*
|
|
136
|
+
* - `grantedServiceIdNamespace` — the absolute serviceId region this
|
|
137
|
+
* connection's provenance may claim (anything at/under it). May be
|
|
138
|
+
* the empty string, meaning "claim nothing freely".
|
|
139
|
+
*/
|
|
140
|
+
get: RequestType<Record<string, never>, {
|
|
141
|
+
grantedServiceIdNamespace: string;
|
|
142
|
+
}, any, never, never>;
|
|
143
|
+
/**
|
|
144
|
+
* The serviceId prefix the hub mounts its own global services under
|
|
145
|
+
* (default `'hub'`). Use it to address the admin-gated
|
|
146
|
+
* `<hubServiceId>::hubServiceIdRegistry::registerServiceId` door and the
|
|
147
|
+
* hub's reflection endpoints. Served at the connection root (unsigned),
|
|
148
|
+
* so a participant can learn it without first knowing where the hub
|
|
149
|
+
* lives.
|
|
150
|
+
*/
|
|
151
|
+
getHubServiceId: RequestType<Record<string, never>, {
|
|
152
|
+
hubServiceId: string;
|
|
153
|
+
}, any, never, never>;
|
|
154
|
+
/**
|
|
155
|
+
* Claim a serviceId prefix **within** this connection's provenance-
|
|
156
|
+
* granted namespace (see `get().grantedServiceIdNamespace`). No
|
|
157
|
+
* capability needed — the grant already happened out-of-band at attach
|
|
158
|
+
* time. The requested `serviceId` must equal the granted namespace or be
|
|
159
|
+
* nested beneath it; anything else is rejected (claim it through the
|
|
160
|
+
* admin-gated `<hubServiceId>::hubServiceIdRegistry::registerServiceId`
|
|
161
|
+
* door instead).
|
|
162
|
+
*/
|
|
163
|
+
register: RequestType<{
|
|
164
|
+
serviceId: string;
|
|
165
|
+
}, Record<string, never>, any, never, never>;
|
|
166
|
+
}>;
|
|
167
|
+
/**
|
|
168
|
+
* `hubAccess::request` — a consumer (e.g. a sandboxed web editor) asks the
|
|
169
|
+
* hub for scoped access to one or more services.
|
|
170
|
+
*
|
|
171
|
+
* Consumers describe their needs as named *dependencies* ("slots"): each
|
|
172
|
+
* slot lists the interfaces the chosen service must implement and the
|
|
173
|
+
* methods the consumer wants to call. The hub resolves candidate services
|
|
174
|
+
* from its participant directory, then forwards the bundle to a host-
|
|
175
|
+
* supplied handler (see `Hub` options) that shows the user a single
|
|
176
|
+
* prompt. The handler picks a concrete service per slot and approves a
|
|
177
|
+
* subset of the requested methods.
|
|
178
|
+
*
|
|
179
|
+
* In v1 the response carries only the resolution (which serviceId was
|
|
180
|
+
* chosen per slot). Once the hub holds a signing identity it will also
|
|
181
|
+
* return a `SignedCapability` the consumer attaches to subsequent calls.
|
|
182
|
+
* Until then, dispatch is not gated on the grant — see plan-access.md.
|
|
183
|
+
*/
|
|
184
|
+
declare const hubAccessInterface: InterfaceDefinition<{
|
|
185
|
+
request: RequestType<{
|
|
186
|
+
consumer: {
|
|
187
|
+
name: string;
|
|
188
|
+
principal: string;
|
|
189
|
+
origin?: string | undefined;
|
|
190
|
+
purpose?: string | undefined;
|
|
191
|
+
};
|
|
192
|
+
dependencies: Record<string, {
|
|
193
|
+
interfaces: {
|
|
194
|
+
id: string;
|
|
195
|
+
hash?: string | undefined;
|
|
196
|
+
required?: boolean | undefined;
|
|
197
|
+
}[];
|
|
198
|
+
members?: {
|
|
199
|
+
interfaceId: string;
|
|
200
|
+
member: {
|
|
201
|
+
exact: string;
|
|
202
|
+
} | {
|
|
203
|
+
prefix: string;
|
|
204
|
+
};
|
|
205
|
+
required?: boolean | undefined;
|
|
206
|
+
}[] | undefined;
|
|
207
|
+
}>;
|
|
208
|
+
duration?: "once" | "shortLived" | "longLived" | "persistent" | undefined;
|
|
209
|
+
}, {
|
|
210
|
+
status: "granted";
|
|
211
|
+
slots: Record<string, {
|
|
212
|
+
serviceId: string;
|
|
213
|
+
satisfiedInterfaces: string[];
|
|
214
|
+
}>;
|
|
215
|
+
capabilities: {
|
|
216
|
+
issuer: string;
|
|
217
|
+
audience: string;
|
|
218
|
+
permissions: {
|
|
219
|
+
target: {
|
|
220
|
+
serviceId: {
|
|
221
|
+
exact: string;
|
|
222
|
+
} | {
|
|
223
|
+
prefix: string;
|
|
224
|
+
};
|
|
225
|
+
interfaceId: {
|
|
226
|
+
exact: string;
|
|
227
|
+
} | {
|
|
228
|
+
prefix: string;
|
|
229
|
+
};
|
|
230
|
+
members: ({
|
|
231
|
+
exact: string;
|
|
232
|
+
} | {
|
|
233
|
+
prefix: string;
|
|
234
|
+
})[];
|
|
235
|
+
interfaceHash?: string | undefined;
|
|
236
|
+
};
|
|
237
|
+
canInvoke?: boolean | undefined;
|
|
238
|
+
canDelegate?: boolean | undefined;
|
|
239
|
+
params?: Record<string, {
|
|
240
|
+
exact: unknown;
|
|
241
|
+
} | {
|
|
242
|
+
enum: unknown[];
|
|
243
|
+
} | {
|
|
244
|
+
prefix: string;
|
|
245
|
+
} | {
|
|
246
|
+
subsetOf: string[];
|
|
247
|
+
} | {
|
|
248
|
+
any: true;
|
|
249
|
+
}> | undefined;
|
|
250
|
+
callBind?: {
|
|
251
|
+
alg: "sha256";
|
|
252
|
+
payloadHash: string;
|
|
253
|
+
} | undefined;
|
|
254
|
+
}[];
|
|
255
|
+
nonce: string;
|
|
256
|
+
$hubrpcSignature: {
|
|
257
|
+
capability?: {
|
|
258
|
+
keyId: string;
|
|
259
|
+
sig: string;
|
|
260
|
+
} | undefined;
|
|
261
|
+
call?: {
|
|
262
|
+
keyId: string;
|
|
263
|
+
sig: string;
|
|
264
|
+
} | undefined;
|
|
265
|
+
};
|
|
266
|
+
expiresAtMs?: number | undefined;
|
|
267
|
+
parentHash?: string | undefined;
|
|
268
|
+
}[];
|
|
269
|
+
} | {
|
|
270
|
+
status: "denied";
|
|
271
|
+
reason?: string | undefined;
|
|
272
|
+
} | {
|
|
273
|
+
status: "noCandidates";
|
|
274
|
+
slots: string[];
|
|
275
|
+
}, any, never, never>;
|
|
276
|
+
/**
|
|
277
|
+
* Service-pinned widening of an existing grant. The consumer asks
|
|
278
|
+
* the hub for additional members on a `serviceId` they already
|
|
279
|
+
* deal with — same audience (their NodeId) and (intended) same
|
|
280
|
+
* `rootIssuer` as the prior grant. The hub never picks the service
|
|
281
|
+
* for the consumer here: `serviceId` is an input, not a result.
|
|
282
|
+
*
|
|
283
|
+
* On grant the response carries a fresh `SignedCapability` whose
|
|
284
|
+
* attenuations cover **only** the granted delta. Bag-compatible
|
|
285
|
+
* with the prior cap; combine via `merge` (when it exists) or
|
|
286
|
+
* just keep both in `$hubrpc.capabilities`.
|
|
287
|
+
*
|
|
288
|
+
* Distinguished from `request` so the consent UI can render a
|
|
289
|
+
* different affordance ("X already has read access on `github`,
|
|
290
|
+
* grant `update` as well?" instead of from-zero selection).
|
|
291
|
+
*
|
|
292
|
+
* TODO(hub-ledger): enforce "consumer has prior history on this
|
|
293
|
+
* service" — see `hub.ts:_handleAccessExtend`. v1 forwards
|
|
294
|
+
* directly to the host's `onAccessExtend` without consulting the
|
|
295
|
+
* `_grants` ledger; this is by design while we settle on the
|
|
296
|
+
* persistence model.
|
|
297
|
+
*/
|
|
298
|
+
extend: RequestType<{
|
|
299
|
+
consumer: {
|
|
300
|
+
name: string;
|
|
301
|
+
principal: string;
|
|
302
|
+
origin?: string | undefined;
|
|
303
|
+
purpose?: string | undefined;
|
|
304
|
+
};
|
|
305
|
+
serviceId: string;
|
|
306
|
+
added: {
|
|
307
|
+
interfaceId: string;
|
|
308
|
+
member: {
|
|
309
|
+
exact: string;
|
|
310
|
+
} | {
|
|
311
|
+
prefix: string;
|
|
312
|
+
};
|
|
313
|
+
required?: boolean | undefined;
|
|
314
|
+
}[];
|
|
315
|
+
duration?: "once" | "shortLived" | "longLived" | "persistent" | undefined;
|
|
316
|
+
}, {
|
|
317
|
+
status: "granted";
|
|
318
|
+
serviceId: string;
|
|
319
|
+
granted: {
|
|
320
|
+
interfaceId: string;
|
|
321
|
+
member: {
|
|
322
|
+
exact: string;
|
|
323
|
+
} | {
|
|
324
|
+
prefix: string;
|
|
325
|
+
};
|
|
326
|
+
}[];
|
|
327
|
+
capabilities?: {
|
|
328
|
+
issuer: string;
|
|
329
|
+
audience: string;
|
|
330
|
+
permissions: {
|
|
331
|
+
target: {
|
|
332
|
+
serviceId: {
|
|
333
|
+
exact: string;
|
|
334
|
+
} | {
|
|
335
|
+
prefix: string;
|
|
336
|
+
};
|
|
337
|
+
interfaceId: {
|
|
338
|
+
exact: string;
|
|
339
|
+
} | {
|
|
340
|
+
prefix: string;
|
|
341
|
+
};
|
|
342
|
+
members: ({
|
|
343
|
+
exact: string;
|
|
344
|
+
} | {
|
|
345
|
+
prefix: string;
|
|
346
|
+
})[];
|
|
347
|
+
interfaceHash?: string | undefined;
|
|
348
|
+
};
|
|
349
|
+
canInvoke?: boolean | undefined;
|
|
350
|
+
canDelegate?: boolean | undefined;
|
|
351
|
+
params?: Record<string, {
|
|
352
|
+
exact: unknown;
|
|
353
|
+
} | {
|
|
354
|
+
enum: unknown[];
|
|
355
|
+
} | {
|
|
356
|
+
prefix: string;
|
|
357
|
+
} | {
|
|
358
|
+
subsetOf: string[];
|
|
359
|
+
} | {
|
|
360
|
+
any: true;
|
|
361
|
+
}> | undefined;
|
|
362
|
+
callBind?: {
|
|
363
|
+
alg: "sha256";
|
|
364
|
+
payloadHash: string;
|
|
365
|
+
} | undefined;
|
|
366
|
+
}[];
|
|
367
|
+
nonce: string;
|
|
368
|
+
$hubrpcSignature: {
|
|
369
|
+
capability?: {
|
|
370
|
+
keyId: string;
|
|
371
|
+
sig: string;
|
|
372
|
+
} | undefined;
|
|
373
|
+
call?: {
|
|
374
|
+
keyId: string;
|
|
375
|
+
sig: string;
|
|
376
|
+
} | undefined;
|
|
377
|
+
};
|
|
378
|
+
expiresAtMs?: number | undefined;
|
|
379
|
+
parentHash?: string | undefined;
|
|
380
|
+
}[] | undefined;
|
|
381
|
+
} | {
|
|
382
|
+
status: "denied";
|
|
383
|
+
reason?: string | undefined;
|
|
384
|
+
}, any, never, never>;
|
|
385
|
+
/**
|
|
386
|
+
* `hubAccess::requestAccess` — direct capability request. The
|
|
387
|
+
* consumer specifies the exact attenuations it wants. No
|
|
388
|
+
* service-discovery, no candidate resolution: the consumer
|
|
389
|
+
* already knows which `(serviceId, interfaceId, members)` it
|
|
390
|
+
* needs, including wildcards (e.g. `serviceId: { prefix: "" }`
|
|
391
|
+
* to ask for an interface anywhere).
|
|
392
|
+
*
|
|
393
|
+
* Compared to `request`:
|
|
394
|
+
* - `request` does directory-based discovery, picks one service
|
|
395
|
+
* per slot, and returns a cap pinned to that service. Use
|
|
396
|
+
* when the consumer says "give me SOME service that does X".
|
|
397
|
+
* - `requestAccess` is verbatim. Use when the consumer says
|
|
398
|
+
* "give me exactly these attenuations". Especially useful for
|
|
399
|
+
* reflection (`hubrpc.directory::list` on any service) and
|
|
400
|
+
* for on-demand per-method grants from an explorer-style UI.
|
|
401
|
+
*
|
|
402
|
+
* The user prompt shows the exact `Capability` the hub will sign
|
|
403
|
+
* on Allow, same byte-equality guarantee as `request`/`extend`.
|
|
404
|
+
*/
|
|
405
|
+
requestAccess: RequestType<{
|
|
406
|
+
consumer: {
|
|
407
|
+
name: string;
|
|
408
|
+
principal: string;
|
|
409
|
+
origin?: string | undefined;
|
|
410
|
+
purpose?: string | undefined;
|
|
411
|
+
};
|
|
412
|
+
permissions: {
|
|
413
|
+
target: {
|
|
414
|
+
serviceId: {
|
|
415
|
+
exact: string;
|
|
416
|
+
} | {
|
|
417
|
+
prefix: string;
|
|
418
|
+
};
|
|
419
|
+
interfaceId: {
|
|
420
|
+
exact: string;
|
|
421
|
+
} | {
|
|
422
|
+
prefix: string;
|
|
423
|
+
};
|
|
424
|
+
members: ({
|
|
425
|
+
exact: string;
|
|
426
|
+
} | {
|
|
427
|
+
prefix: string;
|
|
428
|
+
})[];
|
|
429
|
+
interfaceHash?: string | undefined;
|
|
430
|
+
};
|
|
431
|
+
canInvoke?: boolean | undefined;
|
|
432
|
+
canDelegate?: boolean | undefined;
|
|
433
|
+
params?: Record<string, {
|
|
434
|
+
exact: unknown;
|
|
435
|
+
} | {
|
|
436
|
+
enum: unknown[];
|
|
437
|
+
} | {
|
|
438
|
+
prefix: string;
|
|
439
|
+
} | {
|
|
440
|
+
subsetOf: string[];
|
|
441
|
+
} | {
|
|
442
|
+
any: true;
|
|
443
|
+
}> | undefined;
|
|
444
|
+
callBind?: {
|
|
445
|
+
alg: "sha256";
|
|
446
|
+
payloadHash: string;
|
|
447
|
+
} | undefined;
|
|
448
|
+
callIntent?: {
|
|
449
|
+
method: string;
|
|
450
|
+
nonce: string;
|
|
451
|
+
signedAtMs: number;
|
|
452
|
+
params?: unknown;
|
|
453
|
+
interfaceHash?: string | undefined;
|
|
454
|
+
summary?: string | undefined;
|
|
455
|
+
suggestion?: "once" | "shortLived" | "longLived" | "persistent" | undefined;
|
|
456
|
+
} | undefined;
|
|
457
|
+
}[];
|
|
458
|
+
duration?: "once" | "shortLived" | "longLived" | "persistent" | undefined;
|
|
459
|
+
}, {
|
|
460
|
+
status: "granted";
|
|
461
|
+
capabilities: {
|
|
462
|
+
issuer: string;
|
|
463
|
+
audience: string;
|
|
464
|
+
permissions: {
|
|
465
|
+
target: {
|
|
466
|
+
serviceId: {
|
|
467
|
+
exact: string;
|
|
468
|
+
} | {
|
|
469
|
+
prefix: string;
|
|
470
|
+
};
|
|
471
|
+
interfaceId: {
|
|
472
|
+
exact: string;
|
|
473
|
+
} | {
|
|
474
|
+
prefix: string;
|
|
475
|
+
};
|
|
476
|
+
members: ({
|
|
477
|
+
exact: string;
|
|
478
|
+
} | {
|
|
479
|
+
prefix: string;
|
|
480
|
+
})[];
|
|
481
|
+
interfaceHash?: string | undefined;
|
|
482
|
+
};
|
|
483
|
+
canInvoke?: boolean | undefined;
|
|
484
|
+
canDelegate?: boolean | undefined;
|
|
485
|
+
params?: Record<string, {
|
|
486
|
+
exact: unknown;
|
|
487
|
+
} | {
|
|
488
|
+
enum: unknown[];
|
|
489
|
+
} | {
|
|
490
|
+
prefix: string;
|
|
491
|
+
} | {
|
|
492
|
+
subsetOf: string[];
|
|
493
|
+
} | {
|
|
494
|
+
any: true;
|
|
495
|
+
}> | undefined;
|
|
496
|
+
callBind?: {
|
|
497
|
+
alg: "sha256";
|
|
498
|
+
payloadHash: string;
|
|
499
|
+
} | undefined;
|
|
500
|
+
}[];
|
|
501
|
+
nonce: string;
|
|
502
|
+
$hubrpcSignature: {
|
|
503
|
+
capability?: {
|
|
504
|
+
keyId: string;
|
|
505
|
+
sig: string;
|
|
506
|
+
} | undefined;
|
|
507
|
+
call?: {
|
|
508
|
+
keyId: string;
|
|
509
|
+
sig: string;
|
|
510
|
+
} | undefined;
|
|
511
|
+
};
|
|
512
|
+
expiresAtMs?: number | undefined;
|
|
513
|
+
parentHash?: string | undefined;
|
|
514
|
+
}[];
|
|
515
|
+
} | {
|
|
516
|
+
status: "denied";
|
|
517
|
+
reason?: string | undefined;
|
|
518
|
+
}, any, never, never>;
|
|
519
|
+
}>;
|
|
520
|
+
/**
|
|
521
|
+
* `hubAccessManifest` — the declarative twin of {@link hubAccessInterface}.
|
|
522
|
+
*
|
|
523
|
+
* Where `hubAccess` is the imperative, just-in-time door a consumer *calls* (and
|
|
524
|
+
* the hub serves at the connection root), `hubAccessManifest` is **served by the
|
|
525
|
+
* participant** under its own serviceId, so it appears in `hubrpc.directory::list`
|
|
526
|
+
* — its very presence is the request. A participant publishes its DESIRED access
|
|
527
|
+
* entries (keyed by id, like `request`'s `dependencies`); an admin discovers
|
|
528
|
+
* them via the directory, mints capabilities **with its own identity** (a
|
|
529
|
+
* configured hub capability root), and writes the CURRENT/granted state back via
|
|
530
|
+
* patches. The participant reads the granted entries and attaches the caps to
|
|
531
|
+
* its later calls.
|
|
532
|
+
*
|
|
533
|
+
* Reconcile model (desired vs. current), aligned with the hub's other surfaces:
|
|
534
|
+
* - `getDesired` / `watchDesired` — the participant's declared needs.
|
|
535
|
+
* - `getCurrent` / `setCurrent` / `watchCurrent` — the admin-written grants.
|
|
536
|
+
*
|
|
537
|
+
* `watch*` follows the coarse empty-tick convention of `hubrpc.directory::watch`:
|
|
538
|
+
* a tick means "re-`get` now", keeping the server stateless (no per-item deltas).
|
|
539
|
+
*/
|
|
540
|
+
declare const hubAccessManifestInterface: InterfaceDefinition<{
|
|
541
|
+
/** The full desired document: who is asking and the entries it wants. */
|
|
542
|
+
getDesired: RequestType<Record<string, never>, {
|
|
543
|
+
requested: Record<string, {
|
|
544
|
+
interfaces: {
|
|
545
|
+
id: string;
|
|
546
|
+
hash?: string | undefined;
|
|
547
|
+
required?: boolean | undefined;
|
|
548
|
+
}[];
|
|
549
|
+
kind: "discover";
|
|
550
|
+
consumer: {
|
|
551
|
+
name: string;
|
|
552
|
+
principal: string;
|
|
553
|
+
origin?: string | undefined;
|
|
554
|
+
purpose?: string | undefined;
|
|
555
|
+
};
|
|
556
|
+
members?: {
|
|
557
|
+
interfaceId: string;
|
|
558
|
+
member: {
|
|
559
|
+
exact: string;
|
|
560
|
+
} | {
|
|
561
|
+
prefix: string;
|
|
562
|
+
};
|
|
563
|
+
required?: boolean | undefined;
|
|
564
|
+
}[] | undefined;
|
|
565
|
+
reason?: string | undefined;
|
|
566
|
+
duration?: "once" | "shortLived" | "longLived" | "persistent" | undefined;
|
|
567
|
+
acceptableRootIds?: string[] | undefined;
|
|
568
|
+
origin?: Record<string, unknown> | undefined;
|
|
569
|
+
} | {
|
|
570
|
+
kind: "direct";
|
|
571
|
+
consumer: {
|
|
572
|
+
name: string;
|
|
573
|
+
principal: string;
|
|
574
|
+
origin?: string | undefined;
|
|
575
|
+
purpose?: string | undefined;
|
|
576
|
+
};
|
|
577
|
+
permissions: {
|
|
578
|
+
target: {
|
|
579
|
+
serviceId: {
|
|
580
|
+
exact: string;
|
|
581
|
+
} | {
|
|
582
|
+
prefix: string;
|
|
583
|
+
};
|
|
584
|
+
interfaceId: {
|
|
585
|
+
exact: string;
|
|
586
|
+
} | {
|
|
587
|
+
prefix: string;
|
|
588
|
+
};
|
|
589
|
+
members: ({
|
|
590
|
+
exact: string;
|
|
591
|
+
} | {
|
|
592
|
+
prefix: string;
|
|
593
|
+
})[];
|
|
594
|
+
interfaceHash?: string | undefined;
|
|
595
|
+
};
|
|
596
|
+
canInvoke?: boolean | undefined;
|
|
597
|
+
canDelegate?: boolean | undefined;
|
|
598
|
+
params?: Record<string, {
|
|
599
|
+
exact: unknown;
|
|
600
|
+
} | {
|
|
601
|
+
enum: unknown[];
|
|
602
|
+
} | {
|
|
603
|
+
prefix: string;
|
|
604
|
+
} | {
|
|
605
|
+
subsetOf: string[];
|
|
606
|
+
} | {
|
|
607
|
+
any: true;
|
|
608
|
+
}> | undefined;
|
|
609
|
+
callBind?: {
|
|
610
|
+
alg: "sha256";
|
|
611
|
+
payloadHash: string;
|
|
612
|
+
} | undefined;
|
|
613
|
+
callIntent?: {
|
|
614
|
+
method: string;
|
|
615
|
+
nonce: string;
|
|
616
|
+
signedAtMs: number;
|
|
617
|
+
params?: unknown;
|
|
618
|
+
interfaceHash?: string | undefined;
|
|
619
|
+
summary?: string | undefined;
|
|
620
|
+
suggestion?: "once" | "shortLived" | "longLived" | "persistent" | undefined;
|
|
621
|
+
} | undefined;
|
|
622
|
+
}[];
|
|
623
|
+
reason?: string | undefined;
|
|
624
|
+
duration?: "once" | "shortLived" | "longLived" | "persistent" | undefined;
|
|
625
|
+
acceptableRootIds?: string[] | undefined;
|
|
626
|
+
origin?: Record<string, unknown> | undefined;
|
|
627
|
+
}>;
|
|
628
|
+
revision: number;
|
|
629
|
+
}, void, Record<string, never>, any>;
|
|
630
|
+
/**
|
|
631
|
+
* Coarse change tap on the desired document. Emits an empty tick when
|
|
632
|
+
* `requested` may have changed; the caller re-`getDesired`. Resolves when
|
|
633
|
+
* the caller cancels. Mirrors `hubrpc.directory::watch`.
|
|
634
|
+
*/
|
|
635
|
+
watchDesired: RequestType<Record<string, never>, Record<string, never>, void, any, Record<string, never>>;
|
|
636
|
+
/** The full current document: entryId → granted/denied (absent ⇒ undecided). */
|
|
637
|
+
getCurrent: RequestType<Record<string, never>, {
|
|
638
|
+
current: Record<string, {
|
|
639
|
+
status: "granted";
|
|
640
|
+
granted: {
|
|
641
|
+
serviceId: string;
|
|
642
|
+
satisfiedInterfaces: string[];
|
|
643
|
+
kind: "discover";
|
|
644
|
+
capabilities: {
|
|
645
|
+
issuer: string;
|
|
646
|
+
audience: string;
|
|
647
|
+
permissions: {
|
|
648
|
+
target: {
|
|
649
|
+
serviceId: {
|
|
650
|
+
exact: string;
|
|
651
|
+
} | {
|
|
652
|
+
prefix: string;
|
|
653
|
+
};
|
|
654
|
+
interfaceId: {
|
|
655
|
+
exact: string;
|
|
656
|
+
} | {
|
|
657
|
+
prefix: string;
|
|
658
|
+
};
|
|
659
|
+
members: ({
|
|
660
|
+
exact: string;
|
|
661
|
+
} | {
|
|
662
|
+
prefix: string;
|
|
663
|
+
})[];
|
|
664
|
+
interfaceHash?: string | undefined;
|
|
665
|
+
};
|
|
666
|
+
canInvoke?: boolean | undefined;
|
|
667
|
+
canDelegate?: boolean | undefined;
|
|
668
|
+
params?: Record<string, {
|
|
669
|
+
exact: unknown;
|
|
670
|
+
} | {
|
|
671
|
+
enum: unknown[];
|
|
672
|
+
} | {
|
|
673
|
+
prefix: string;
|
|
674
|
+
} | {
|
|
675
|
+
subsetOf: string[];
|
|
676
|
+
} | {
|
|
677
|
+
any: true;
|
|
678
|
+
}> | undefined;
|
|
679
|
+
callBind?: {
|
|
680
|
+
alg: "sha256";
|
|
681
|
+
payloadHash: string;
|
|
682
|
+
} | undefined;
|
|
683
|
+
}[];
|
|
684
|
+
nonce: string;
|
|
685
|
+
$hubrpcSignature: {
|
|
686
|
+
capability?: {
|
|
687
|
+
keyId: string;
|
|
688
|
+
sig: string;
|
|
689
|
+
} | undefined;
|
|
690
|
+
call?: {
|
|
691
|
+
keyId: string;
|
|
692
|
+
sig: string;
|
|
693
|
+
} | undefined;
|
|
694
|
+
};
|
|
695
|
+
expiresAtMs?: number | undefined;
|
|
696
|
+
parentHash?: string | undefined;
|
|
697
|
+
}[];
|
|
698
|
+
} | {
|
|
699
|
+
kind: "direct";
|
|
700
|
+
capabilities: {
|
|
701
|
+
issuer: string;
|
|
702
|
+
audience: string;
|
|
703
|
+
permissions: {
|
|
704
|
+
target: {
|
|
705
|
+
serviceId: {
|
|
706
|
+
exact: string;
|
|
707
|
+
} | {
|
|
708
|
+
prefix: string;
|
|
709
|
+
};
|
|
710
|
+
interfaceId: {
|
|
711
|
+
exact: string;
|
|
712
|
+
} | {
|
|
713
|
+
prefix: string;
|
|
714
|
+
};
|
|
715
|
+
members: ({
|
|
716
|
+
exact: string;
|
|
717
|
+
} | {
|
|
718
|
+
prefix: string;
|
|
719
|
+
})[];
|
|
720
|
+
interfaceHash?: string | undefined;
|
|
721
|
+
};
|
|
722
|
+
canInvoke?: boolean | undefined;
|
|
723
|
+
canDelegate?: boolean | undefined;
|
|
724
|
+
params?: Record<string, {
|
|
725
|
+
exact: unknown;
|
|
726
|
+
} | {
|
|
727
|
+
enum: unknown[];
|
|
728
|
+
} | {
|
|
729
|
+
prefix: string;
|
|
730
|
+
} | {
|
|
731
|
+
subsetOf: string[];
|
|
732
|
+
} | {
|
|
733
|
+
any: true;
|
|
734
|
+
}> | undefined;
|
|
735
|
+
callBind?: {
|
|
736
|
+
alg: "sha256";
|
|
737
|
+
payloadHash: string;
|
|
738
|
+
} | undefined;
|
|
739
|
+
}[];
|
|
740
|
+
nonce: string;
|
|
741
|
+
$hubrpcSignature: {
|
|
742
|
+
capability?: {
|
|
743
|
+
keyId: string;
|
|
744
|
+
sig: string;
|
|
745
|
+
} | undefined;
|
|
746
|
+
call?: {
|
|
747
|
+
keyId: string;
|
|
748
|
+
sig: string;
|
|
749
|
+
} | undefined;
|
|
750
|
+
};
|
|
751
|
+
expiresAtMs?: number | undefined;
|
|
752
|
+
parentHash?: string | undefined;
|
|
753
|
+
}[];
|
|
754
|
+
};
|
|
755
|
+
} | {
|
|
756
|
+
status: "denied";
|
|
757
|
+
reason?: string | undefined;
|
|
758
|
+
}>;
|
|
759
|
+
revision: number;
|
|
760
|
+
}, any, never, never>;
|
|
761
|
+
/**
|
|
762
|
+
* Apply patches to the current document. Replace the whole document with
|
|
763
|
+
* `{ op: 'set', path: '', value }`, or a single entry with
|
|
764
|
+
* `{ op: 'set', path: '/current/<entryId>', value }` (a `zCurrentEntry`:
|
|
765
|
+
* granted or denied). Admin-only in practice (gated by a capability
|
|
766
|
+
* rooted at an accepted issuer).
|
|
767
|
+
*/
|
|
768
|
+
setCurrent: RequestType<{
|
|
769
|
+
patches: ({
|
|
770
|
+
op: "set";
|
|
771
|
+
path: string;
|
|
772
|
+
value: unknown;
|
|
773
|
+
} | {
|
|
774
|
+
op: "remove";
|
|
775
|
+
path: string;
|
|
776
|
+
})[];
|
|
777
|
+
}, {
|
|
778
|
+
revision: number;
|
|
779
|
+
}, void, Record<string, never>, any>;
|
|
780
|
+
/**
|
|
781
|
+
* Coarse change tap on the current document. Emits an empty tick when
|
|
782
|
+
* `current` may have changed; the participant re-`getCurrent` and applies
|
|
783
|
+
* any new capabilities. Resolves when the caller cancels.
|
|
784
|
+
*/
|
|
785
|
+
watchCurrent: RequestType<Record<string, never>, Record<string, never>, void, any, Record<string, never>>;
|
|
786
|
+
}>;
|
|
787
|
+
/**
|
|
788
|
+
* The typed client shape of {@link hubAccessManifestInterface} — the exact
|
|
789
|
+
* object `connection.get(hubAccessManifestInterface)` (or
|
|
790
|
+
* `connection.service(id).get(...)`) returns. Approvers depend on this contract,
|
|
791
|
+
* not on a concrete connection: the same approver runs against a local in-memory
|
|
792
|
+
* host (loopback connection), a remote hub's served manifest, or a future
|
|
793
|
+
* aggregating implementation with no code change.
|
|
794
|
+
*/
|
|
795
|
+
type IHubAccessManifest = InterfaceClient<typeof hubAccessManifestInterface>;
|
|
796
|
+
/** One typed entry from `hubAccessManifest::getDesired().requested`. */
|
|
797
|
+
type HubAccessManifestRequest = Awaited<ReturnType<IHubAccessManifest['getDesired']>>['requested'][string];
|
|
798
|
+
/** One typed value accepted at `/current/<entryId>` by `setCurrent`. */
|
|
799
|
+
type HubAccessManifestDecision = Awaited<ReturnType<IHubAccessManifest['getCurrent']>>['current'][string];
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region src/hub/common/baseCapabilities.d.ts
|
|
802
|
+
/**
|
|
803
|
+
* Claim a serviceId prefix within this connection's provenance-granted
|
|
804
|
+
* namespace via `hubGrantedServiceId::register` (no capability needed).
|
|
805
|
+
* Resolves once the claim is registered. Rejects when the prefix is outside
|
|
806
|
+
* the granted namespace or already owned.
|
|
807
|
+
*/
|
|
808
|
+
declare function registerGrantedServiceId(sender: IRequestSender<SigningCallCtx>, serviceId: string): Promise<void>;
|
|
809
|
+
//#endregion
|
|
810
|
+
//#region src/hub/common/managedSigning.d.ts
|
|
811
|
+
/** Who is asking — surfaced in the hub's consent prompt. */
|
|
812
|
+
interface AutoNegotiateConsumer {
|
|
813
|
+
readonly name: string;
|
|
814
|
+
readonly origin?: string;
|
|
815
|
+
readonly purpose?: string;
|
|
816
|
+
}
|
|
817
|
+
/** Options for {@link createManagedSigningChannel}. */
|
|
818
|
+
interface ManagedSigningOptions {
|
|
819
|
+
/**
|
|
820
|
+
* When set, install a sign-time {@link CapProvider} that lazily negotiates
|
|
821
|
+
* the capability each gated call needs through the hub's `hubAccess`
|
|
822
|
+
* consent front door. Durable grants are absorbed into the principal's cap
|
|
823
|
+
* bag so later calls present them automatically; one-shot grants are
|
|
824
|
+
* attached to just the call that prompted them.
|
|
825
|
+
*
|
|
826
|
+
* Without it, the channel signs every call but presents no capabilities —
|
|
827
|
+
* gated calls then fail with `permissionRequired` unless the caller has
|
|
828
|
+
* arranged a capability some other way.
|
|
829
|
+
*/
|
|
830
|
+
readonly autoNegotiateCaps?: boolean;
|
|
831
|
+
/** Consumer identity shown in the consent prompt. */
|
|
832
|
+
readonly consumer?: AutoNegotiateConsumer;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Build a managed-identity signing {@link Channel} from a raw channel, with
|
|
836
|
+
* optional per-call capability auto-negotiation.
|
|
837
|
+
*
|
|
838
|
+
* Like {@link SigningSender.fromChannelWithManagedPrincipal}, the
|
|
839
|
+
* `identity::*` bootstrap rides the raw (unsigned) sender, and the returned
|
|
840
|
+
* signing channel is what callers hand to `LinkRpcConnection`. When
|
|
841
|
+
* {@link ManagedSigningOptions.autoNegotiateCaps} is set, the returned
|
|
842
|
+
* channel additionally negotiates capabilities on demand — the browser-safe
|
|
843
|
+
* equivalent of the CLI's `setupSigning({ autoNegotiatePerCall: true })`.
|
|
844
|
+
*/
|
|
845
|
+
declare function createManagedSigningChannel(channel: Channel<undefined>, opts?: ManagedSigningOptions): Promise<ManagedSigningChannel<undefined>>;
|
|
846
|
+
/**
|
|
847
|
+
* A sign-time {@link CapProvider} that negotiates access per gated call.
|
|
848
|
+
*
|
|
849
|
+
* For each outbound call it: presents any fresh caps already in the bag; if
|
|
850
|
+
* none cover the call, bootstraps the root `hubAccess` capability (once), then
|
|
851
|
+
* asks `hubAccess::requestAccess` for a grant scoped to exactly this call
|
|
852
|
+
* (`callIntent` pinned to the method/params/nonce). Durable grants are added to
|
|
853
|
+
* the bag; a one-shot grant is attached to just this call.
|
|
854
|
+
*
|
|
855
|
+
* Fail-soft: any negotiation error (open hub, denied consent, unreachable front
|
|
856
|
+
* door) falls back to presenting the current bag, letting the call surface
|
|
857
|
+
* `permissionRequired` if it is truly gated.
|
|
858
|
+
*/
|
|
859
|
+
declare function createAutoNegotiatingCapProvider(opts: {
|
|
860
|
+
readonly sender: IRequestSender<SigningCallCtx>;
|
|
861
|
+
readonly principal: Principal;
|
|
862
|
+
readonly consumer: AutoNegotiateConsumer;
|
|
863
|
+
}): CapProvider;
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region src/hub/common/directoryWalk.d.ts
|
|
866
|
+
/**
|
|
867
|
+
* Portable reflection walk over the `hubrpc.directory` referral tree.
|
|
868
|
+
*
|
|
869
|
+
* A directory may explicitly list other `hubrpc.directory` services as
|
|
870
|
+
* referrals. Flattening that graph into an interface inventory is the
|
|
871
|
+
* consumer's job. Routing state is deliberately not part of this contract.
|
|
872
|
+
*/
|
|
873
|
+
/**
|
|
874
|
+
* The sender reflection helpers speak over: the signing decorator that wraps
|
|
875
|
+
* the live channel. Decoupled from the concrete `JsonRpcChannel` so reconnect
|
|
876
|
+
* can swap the underlying channel transparently.
|
|
877
|
+
*/
|
|
878
|
+
type ReflectionChannel = IRequestSender<SigningCallCtx>;
|
|
879
|
+
interface ServiceListing {
|
|
880
|
+
readonly serviceId: string;
|
|
881
|
+
readonly interfaceId: string;
|
|
882
|
+
readonly hash: string;
|
|
883
|
+
readonly path?: string;
|
|
884
|
+
/** Optional non-normative description of the owning service. */
|
|
885
|
+
readonly serviceDescription?: string;
|
|
886
|
+
/**
|
|
887
|
+
* Root node ids required to access the owning service, in CNF. Surfaced by
|
|
888
|
+
* the directory; {@link walkHubDetailed} additionally folds transitive
|
|
889
|
+
* requirements from ancestor directories onto descendants.
|
|
890
|
+
*/
|
|
891
|
+
readonly rootPrincipalSets?: readonly RootPrincipalSet[];
|
|
892
|
+
/** Scope carried by a directory referral; omitted means its service-id subtree. */
|
|
893
|
+
readonly reachableServiceIds?: readonly ServiceIdPattern[];
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* A {@link ServiceListing} enriched with the service id of the directory that
|
|
897
|
+
* reported it. This is provenance only: every listing is asserted reachable by
|
|
898
|
+
* sending to the endpoint that answered the directory.
|
|
899
|
+
*/
|
|
900
|
+
interface DiscoveredListing extends ServiceListing {
|
|
901
|
+
readonly discoveredFrom: string;
|
|
902
|
+
}
|
|
903
|
+
interface ListOptions {
|
|
904
|
+
readonly interfaceId?: string;
|
|
905
|
+
readonly interfaceIdPrefix?: string;
|
|
906
|
+
readonly serviceId?: string;
|
|
907
|
+
readonly serviceIdScopes?: readonly ServiceIdPattern[];
|
|
908
|
+
readonly limit?: number;
|
|
909
|
+
readonly timeoutMs?: number;
|
|
910
|
+
/**
|
|
911
|
+
* When set, send the reflection call to that service via form-3
|
|
912
|
+
* (`<target>::hubrpc.directory::list`) instead of the implicit root
|
|
913
|
+
* directory. Used when talking to a hub: the root directory is the hub
|
|
914
|
+
* itself, but per-service reflection lives on each participant.
|
|
915
|
+
*/
|
|
916
|
+
readonly target?: string;
|
|
917
|
+
}
|
|
918
|
+
declare function fetchDirectory(channel: ReflectionChannel, opts?: ListOptions): Promise<ServiceListing[]>;
|
|
919
|
+
declare function fetchSchema(channel: ReflectionChannel, interfaceId: string, hash: string | undefined,
|
|
920
|
+
/**
|
|
921
|
+
* Optional routing target. When set, the schema request is addressed to
|
|
922
|
+
* `<target>::hubrpc.schemas::get` (form-3) so it reaches the service that
|
|
923
|
+
* actually hosts this interface — needed when talking to a hub and the
|
|
924
|
+
* interface lives behind a participant.
|
|
925
|
+
*/
|
|
926
|
+
target?: string): Promise<LinkRpcInterfaceSchema>;
|
|
927
|
+
declare function findMethodInSchema(schema: LinkRpcInterfaceSchema, methodName: string): MethodSchema | undefined;
|
|
928
|
+
/** Default recursion depth when walking the bus. */
|
|
929
|
+
declare const DEFAULT_WALK_DEPTH = 5;
|
|
930
|
+
/** Segment-aware service-id pattern matching (`foo` includes `foo/bar`, not `foobar`). */
|
|
931
|
+
declare function serviceIdMatchesPattern(serviceId: string, pattern: ServiceIdPattern): boolean;
|
|
932
|
+
/** Whether a service id is included by a union of patterns. `undefined` means unfiltered. */
|
|
933
|
+
declare function serviceIdMatchesScopes(serviceId: string, scopes: readonly ServiceIdPattern[] | undefined): boolean;
|
|
934
|
+
/** Canonicalize a pattern union by removing duplicates and contained patterns. */
|
|
935
|
+
declare function normalizeServiceIdScopes(scopes: readonly ServiceIdPattern[]): ServiceIdPattern[];
|
|
936
|
+
/** Pairwise intersection of two pattern unions. */
|
|
937
|
+
declare function intersectServiceIdScopes(left: readonly ServiceIdPattern[], right: readonly ServiceIdPattern[]): ServiceIdPattern[];
|
|
938
|
+
/**
|
|
939
|
+
* A sub-directory the walk surfaced but could not enumerate — typically because
|
|
940
|
+
* it is capability-gated (e.g. the `hub` directory, reachable only once the
|
|
941
|
+
* connection holds a capability for it). The services it would have listed are
|
|
942
|
+
* therefore absent from the walk; callers can surface `reason` to explain the
|
|
943
|
+
* gap rather than silently dropping the branch.
|
|
944
|
+
*/
|
|
945
|
+
interface InaccessibleDirectory {
|
|
946
|
+
/** The directory target (serviceId) that could not be enumerated. */
|
|
947
|
+
readonly serviceId: string;
|
|
948
|
+
/** The error message from the denied directory lookup. */
|
|
949
|
+
readonly reason: string;
|
|
950
|
+
}
|
|
951
|
+
interface WalkHubResult {
|
|
952
|
+
/** Every interface reachable from the directories the walk could read. */
|
|
953
|
+
readonly listings: DiscoveredListing[];
|
|
954
|
+
/**
|
|
955
|
+
* Sub-directories that were referenced but could not be enumerated (e.g.
|
|
956
|
+
* capability-gated). Their services are not present in `listings`.
|
|
957
|
+
*/
|
|
958
|
+
readonly inaccessible: InaccessibleDirectory[];
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Recursive graph walk: list a directory, intersect the accumulated service-id
|
|
962
|
+
* scope with every explicit `hubrpc.directory` referral, and recurse. Bounded
|
|
963
|
+
* by `maxDepth`.
|
|
964
|
+
*
|
|
965
|
+
* Every listing is tagged with `discoveredFrom` — the serviceId of the
|
|
966
|
+
* directory that produced it. When the same `(serviceId, interfaceId, hash)`
|
|
967
|
+
* shows up via several directories (e.g. once forwarded by the hub, once
|
|
968
|
+
* directly from the participant), we keep the one whose `discoveredFrom`
|
|
969
|
+
* matches the item's `serviceId`, since that's the directory that
|
|
970
|
+
* authoritatively knows about it.
|
|
971
|
+
*
|
|
972
|
+
* Transitive root-node-id requirements flow down the tree: when the walk
|
|
973
|
+
* recurses into a directory whose own `rootPrincipalSets` include `transitive`
|
|
974
|
+
* reqs, every interface discovered at or below that directory inherits them (as
|
|
975
|
+
* singleton AND-sets, kept transitive so they keep flowing further down). This
|
|
976
|
+
* replaces the hub-side `applyTransitiveReqs` fold that the v1 aggregating
|
|
977
|
+
* directory performed.
|
|
978
|
+
*
|
|
979
|
+
* A sub-directory the walk cannot read is recorded in
|
|
980
|
+
* {@link WalkHubResult.inaccessible} rather than silently dropped. A failure to
|
|
981
|
+
* read the *root* directory is left silent — there is no sub-tree to explain —
|
|
982
|
+
* and simply yields empty results.
|
|
983
|
+
*
|
|
984
|
+
* When {@link WalkHubOptions.unlockGatedDirectory} is supplied, each gated
|
|
985
|
+
* sub-directory is offered to the hook; if it returns `true` (a capability was
|
|
986
|
+
* granted), that directory is re-queued and the walk continues into it — a
|
|
987
|
+
* fixpoint that keeps unlocking newly-revealed gated branches until nothing
|
|
988
|
+
* more can be opened (still bounded by `maxDepth`).
|
|
989
|
+
*/
|
|
990
|
+
interface WalkHubOptions {
|
|
991
|
+
readonly maxDepth?: number;
|
|
992
|
+
/** Hard deadline for each directory page request. Defaults to 5 seconds. */
|
|
993
|
+
readonly timeoutMs?: number;
|
|
994
|
+
/** Return only listings for this exact interface id after traversing directories. */
|
|
995
|
+
readonly interfaceId?: string;
|
|
996
|
+
/** Return only listings whose interface id starts with this prefix after traversal. */
|
|
997
|
+
readonly interfaceIdPrefix?: string;
|
|
998
|
+
/** Return only listings for this exact service id after traversal. */
|
|
999
|
+
readonly serviceId?: string;
|
|
1000
|
+
/** Initial service-id scope. Defaults to the universal `{ prefix: "" }`. */
|
|
1001
|
+
readonly serviceIdScopes?: readonly ServiceIdPattern[];
|
|
1002
|
+
/**
|
|
1003
|
+
* The directory the walk starts from. Defaults to the implicit root
|
|
1004
|
+
* directory (form-2 `hubrpc.directory::list`). Pass a serviceId to start at
|
|
1005
|
+
* `<rootTarget>::hubrpc.directory::list` — e.g. the hub's own global
|
|
1006
|
+
* directory (`'hub'`) when walking from the hub's in-process connection.
|
|
1007
|
+
*/
|
|
1008
|
+
readonly rootTarget?: string;
|
|
1009
|
+
/**
|
|
1010
|
+
* Invoked once per gated sub-directory the walk encounters. Return `true`
|
|
1011
|
+
* if a capability for its `hubrpc.directory::list` was granted and the
|
|
1012
|
+
* directory should be re-listed; `false` (or omitted hook) leaves it in
|
|
1013
|
+
* {@link WalkHubResult.inaccessible}. Called at most once per `serviceId`.
|
|
1014
|
+
*/
|
|
1015
|
+
unlockGatedDirectory?: (serviceId: string) => Promise<boolean>;
|
|
1016
|
+
/** Optional diagnostic sink for inaccessible lists and ended watches. */
|
|
1017
|
+
readonly log?: (message: string) => void;
|
|
1018
|
+
}
|
|
1019
|
+
declare function walkHubDetailed(channel: ReflectionChannel, opts?: WalkHubOptions): Promise<WalkHubResult>;
|
|
1020
|
+
/**
|
|
1021
|
+
* Backward-compatible wrapper over {@link walkHubDetailed} that returns only the
|
|
1022
|
+
* reachable listings, dropping the inaccessible-directory report.
|
|
1023
|
+
*/
|
|
1024
|
+
declare function walkHub(channel: ReflectionChannel, opts?: WalkHubOptions): Promise<DiscoveredListing[]>;
|
|
1025
|
+
interface HubDirectoryChange {
|
|
1026
|
+
/** Directory that emitted the tick. `undefined` denotes the connection root. */
|
|
1027
|
+
readonly target: string | undefined;
|
|
1028
|
+
readonly result: WalkHubResult;
|
|
1029
|
+
}
|
|
1030
|
+
type HubDirectoryGraphTarget = {
|
|
1031
|
+
readonly kind: 'root';
|
|
1032
|
+
readonly serviceId?: string;
|
|
1033
|
+
} | {
|
|
1034
|
+
readonly kind: 'addressed';
|
|
1035
|
+
readonly serviceId: string;
|
|
1036
|
+
};
|
|
1037
|
+
type HubDirectoryNodeState = 'unexplored' | 'loading' | 'loaded' | 'inaccessible';
|
|
1038
|
+
interface HubDirectoryParentReport {
|
|
1039
|
+
readonly target: HubDirectoryGraphTarget;
|
|
1040
|
+
readonly scopes: readonly ServiceIdPattern[];
|
|
1041
|
+
/** Resulting depth for this incoming parent path. */
|
|
1042
|
+
readonly depth: number;
|
|
1043
|
+
}
|
|
1044
|
+
/** Stable public report for one native directory response. */
|
|
1045
|
+
interface HubDirectoryNodeReport {
|
|
1046
|
+
readonly target: HubDirectoryGraphTarget;
|
|
1047
|
+
readonly state: HubDirectoryNodeState;
|
|
1048
|
+
readonly effectiveScopes: readonly ServiceIdPattern[];
|
|
1049
|
+
readonly depth: number;
|
|
1050
|
+
readonly parents: readonly HubDirectoryParentReport[];
|
|
1051
|
+
/** Per-directory listings, normalized to `ServiceListing`, before inherited metadata is folded. */
|
|
1052
|
+
readonly nativeListings: readonly ServiceListing[];
|
|
1053
|
+
readonly inaccessibleReason?: string;
|
|
1054
|
+
readonly watching: boolean;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Complete point-in-time graph document. `root` exists from construction in
|
|
1058
|
+
* the `unexplored` state, so consumers can render before exploration starts.
|
|
1059
|
+
*/
|
|
1060
|
+
interface HubDirectoryGraphSnapshot {
|
|
1061
|
+
readonly revision: number;
|
|
1062
|
+
readonly complete: boolean;
|
|
1063
|
+
readonly root: HubDirectoryNodeReport;
|
|
1064
|
+
readonly directories: readonly HubDirectoryNodeReport[];
|
|
1065
|
+
readonly result: WalkHubResult;
|
|
1066
|
+
}
|
|
1067
|
+
type HubDirectoryGraphEvent = {
|
|
1068
|
+
readonly type: 'snapshot';
|
|
1069
|
+
readonly reason: 'subscribed' | 'reconciliation-started' | 'reset';
|
|
1070
|
+
readonly snapshot: HubDirectoryGraphSnapshot;
|
|
1071
|
+
} | {
|
|
1072
|
+
readonly type: 'node-added';
|
|
1073
|
+
readonly reason: 'referral';
|
|
1074
|
+
readonly node: HubDirectoryNodeReport;
|
|
1075
|
+
readonly snapshot: HubDirectoryGraphSnapshot;
|
|
1076
|
+
} | {
|
|
1077
|
+
readonly type: 'node-updated';
|
|
1078
|
+
readonly reason: 'loading' | 'loaded' | 'inaccessible' | 'referral' | 'watch-started' | 'watch-stopped' | 'watch-ended' | 'reset';
|
|
1079
|
+
readonly previous: HubDirectoryNodeReport;
|
|
1080
|
+
readonly node: HubDirectoryNodeReport;
|
|
1081
|
+
readonly snapshot: HubDirectoryGraphSnapshot;
|
|
1082
|
+
} | {
|
|
1083
|
+
readonly type: 'node-removed';
|
|
1084
|
+
readonly reason: 'unreachable' | 'reset';
|
|
1085
|
+
readonly previous: HubDirectoryNodeReport;
|
|
1086
|
+
readonly snapshot: HubDirectoryGraphSnapshot;
|
|
1087
|
+
} | {
|
|
1088
|
+
readonly type: 'settled';
|
|
1089
|
+
readonly snapshot: HubDirectoryGraphSnapshot;
|
|
1090
|
+
};
|
|
1091
|
+
/**
|
|
1092
|
+
* Reusable directory graph explorer and watcher.
|
|
1093
|
+
*
|
|
1094
|
+
* The class keeps one native snapshot per directory target. A watch tick
|
|
1095
|
+
* re-lists only its source target; outgoing referral changes then propagate
|
|
1096
|
+
* through affected descendants. Unchanged siblings retain both their snapshot
|
|
1097
|
+
* and watch. Multi-parent targets are queried with the normalized union of all
|
|
1098
|
+
* incoming path scopes.
|
|
1099
|
+
*/
|
|
1100
|
+
declare class HubDirectoryExplorer {
|
|
1101
|
+
private readonly _channel;
|
|
1102
|
+
private readonly _options;
|
|
1103
|
+
private readonly _maxDepth;
|
|
1104
|
+
private readonly _initialScopes;
|
|
1105
|
+
private readonly _nodes;
|
|
1106
|
+
private readonly _unlockAttempted;
|
|
1107
|
+
private readonly _listeners;
|
|
1108
|
+
private readonly _graphListeners;
|
|
1109
|
+
private readonly _pendingTicks;
|
|
1110
|
+
private readonly _watchRetryTimers;
|
|
1111
|
+
private readonly _watchRetryDelays;
|
|
1112
|
+
private _initialized;
|
|
1113
|
+
private _disposed;
|
|
1114
|
+
private _watching;
|
|
1115
|
+
private _complete;
|
|
1116
|
+
private _revision;
|
|
1117
|
+
private _work;
|
|
1118
|
+
private _explorePromise;
|
|
1119
|
+
constructor(_channel: ReflectionChannel, _options?: WalkHubOptions);
|
|
1120
|
+
/** Perform a fresh bounded exploration without starting watches. */
|
|
1121
|
+
explore(): Promise<WalkHubResult>;
|
|
1122
|
+
private _explore;
|
|
1123
|
+
/** Current deduplicated graph snapshot. */
|
|
1124
|
+
get result(): WalkHubResult;
|
|
1125
|
+
/** Current complete-or-progressive directory graph report. */
|
|
1126
|
+
get graphSnapshot(): HubDirectoryGraphSnapshot;
|
|
1127
|
+
/**
|
|
1128
|
+
* Subscribe to progressive graph reports. The listener is registered before
|
|
1129
|
+
* receiving a synchronous current snapshot, avoiding the subscribe/explore
|
|
1130
|
+
* race for both idle and already-running explorers.
|
|
1131
|
+
*/
|
|
1132
|
+
subscribe(listener: (event: HubDirectoryGraphEvent) => void): () => void;
|
|
1133
|
+
/**
|
|
1134
|
+
* Explore once, then watch every reachable directory. Resolves after the
|
|
1135
|
+
* initial graph and watches are established.
|
|
1136
|
+
*/
|
|
1137
|
+
watch(listener: (change: HubDirectoryChange) => void): Promise<() => void>;
|
|
1138
|
+
/** Wait until all currently queued watch reconciliation has settled. */
|
|
1139
|
+
whenIdle(): Promise<void>;
|
|
1140
|
+
dispose(): void;
|
|
1141
|
+
private _loadNode;
|
|
1142
|
+
private _tryUnlock;
|
|
1143
|
+
private _reconcileOutgoing;
|
|
1144
|
+
private _outgoingFor;
|
|
1145
|
+
private _createRootNode;
|
|
1146
|
+
private _createNode;
|
|
1147
|
+
private _updateNodeFromParents;
|
|
1148
|
+
private _deriveNodeFromParents;
|
|
1149
|
+
private _derivedNodeState;
|
|
1150
|
+
/**
|
|
1151
|
+
* Rebuild incoming contributions from the root seed. Starting with no
|
|
1152
|
+
* non-root contributions computes the least fixed point, so a disconnected
|
|
1153
|
+
* or narrowed cycle cannot keep scopes contributed by its previous state.
|
|
1154
|
+
*/
|
|
1155
|
+
private _reconcileScopesFromRoot;
|
|
1156
|
+
private _computeScopeFixedPoint;
|
|
1157
|
+
private _pruneUnreachable;
|
|
1158
|
+
/**
|
|
1159
|
+
* Every changed watch scope uses list/watch/list: callers reach this method
|
|
1160
|
+
* only after a list under the current scope, then the replacement watch is
|
|
1161
|
+
* installed before a verification list. Either the verification sees a
|
|
1162
|
+
* handoff mutation or the new watch ticks for it.
|
|
1163
|
+
*/
|
|
1164
|
+
private _synchronizeWatches;
|
|
1165
|
+
private _replaceWatch;
|
|
1166
|
+
private _queueTick;
|
|
1167
|
+
private _stopWatch;
|
|
1168
|
+
private _stopAllWatches;
|
|
1169
|
+
private _scheduleWatchRetry;
|
|
1170
|
+
private _clearWatchRetry;
|
|
1171
|
+
private _clearNodes;
|
|
1172
|
+
private _resetForExplore;
|
|
1173
|
+
private _updateNode;
|
|
1174
|
+
private _nodeReport;
|
|
1175
|
+
private _targetForKey;
|
|
1176
|
+
private _emitNodeAdded;
|
|
1177
|
+
private _emitNodeUpdated;
|
|
1178
|
+
private _emitNodeRemoved;
|
|
1179
|
+
private _startReconciliation;
|
|
1180
|
+
private _settle;
|
|
1181
|
+
private _emitSnapshot;
|
|
1182
|
+
private _emitGraphEvent;
|
|
1183
|
+
private _deliverGraphEvent;
|
|
1184
|
+
private _nodeKeyForTarget;
|
|
1185
|
+
private _throwIfDisposed;
|
|
1186
|
+
}
|
|
1187
|
+
//#endregion
|
|
1188
|
+
export { type AutoNegotiateConsumer, DEFAULT_WALK_DEPTH, type DiscoveredListing, type HubAccessDuration, type HubAccessManifestDecision, type HubAccessManifestRequest, type HubAccessPattern, type HubAccessPermission, type HubAccessRequest, type HubAccessResult, type HubDirectoryChange, HubDirectoryExplorer, type HubDirectoryGraphEvent, type HubDirectoryGraphSnapshot, type HubDirectoryGraphTarget, type HubDirectoryNodeReport, type HubDirectoryNodeState, type HubDirectoryParentReport, type IHubAccessManifest, type ITransportServer, type InaccessibleDirectory, type ListOptions, type ManagedSigningOptions, ROOT_SERVICE_ID, type ReflectionChannel, SERVICE_ID_SEPARATOR, type ServiceId, type ServiceListing, type Transport, type WalkHubOptions, type WalkHubResult, createAutoNegotiatingCapProvider, createManagedSigningChannel, fetchDirectory, fetchSchema, findCoveringCapabilities, findMethodInSchema, hubAccessInterface, hubAccessManifestInterface, hubGrantedServiceIdInterface, hubServiceIdRegistryInterface, intersectServiceIdScopes, isServiceIdUnder, isValidServiceId, mapTransport, normalizeServiceIdScopes, registerGrantedServiceId, serviceIdMatchesPattern, serviceIdMatchesScopes, splitServiceId, walkHub, walkHubDetailed };
|
|
1189
|
+
//# sourceMappingURL=index.d.ts.map
|