@hediet/linkrpc-hub 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.
Files changed (50) hide show
  1. package/README.md +101 -0
  2. package/dist/chunks/config-BCvkg7jv.d.ts +566 -0
  3. package/dist/chunks/configFile-y6Phntqu.d.ts +9 -0
  4. package/dist/chunks/connectionTokenBinder.interfaces-B-beCg06.js +40 -0
  5. package/dist/chunks/connectionTokenBinder.interfaces-B-beCg06.js.map +1 -0
  6. package/dist/chunks/connectionTokenBinder.interfaces-BhzQ1DTS.d.ts +36 -0
  7. package/dist/chunks/hubConnectionAcceptor-B5-8JsFY.js +2768 -0
  8. package/dist/chunks/hubConnectionAcceptor-B5-8JsFY.js.map +1 -0
  9. package/dist/chunks/hubConnectionAcceptor-BwydvFa4.d.ts +1560 -0
  10. package/dist/chunks/index-CLIUrV88.d.ts +481 -0
  11. package/dist/chunks/node-CTXsQ6oa.js +460 -0
  12. package/dist/chunks/node-CTXsQ6oa.js.map +1 -0
  13. package/dist/chunks/nodeTransit-CWeFnbwt.js +444 -0
  14. package/dist/chunks/nodeTransit-CWeFnbwt.js.map +1 -0
  15. package/dist/chunks/nodeTransit-cmtZgdpW.d.ts +226 -0
  16. package/dist/chunks/runHub-P3YUwwdv.js +1797 -0
  17. package/dist/chunks/runHub-P3YUwwdv.js.map +1 -0
  18. package/dist/chunks/server-BAxchQhy.js +1368 -0
  19. package/dist/chunks/server-BAxchQhy.js.map +1 -0
  20. package/dist/cli.d.ts +1 -0
  21. package/dist/cli.js +38 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/config.d.ts +2 -0
  24. package/dist/config.js +305 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/configFile.d.ts +2 -0
  27. package/dist/configFile.js +27 -0
  28. package/dist/configFile.js.map +1 -0
  29. package/dist/engine/runHub.d.ts +42 -0
  30. package/dist/engine/runHub.js +2 -0
  31. package/dist/hub/server/client.d.ts +2 -0
  32. package/dist/hub/server/client.js +2 -0
  33. package/dist/hub/server/connectionTokenBinder.d.ts +2 -0
  34. package/dist/hub/server/connectionTokenBinder.js +2 -0
  35. package/dist/hub/server/index.d.ts +5 -0
  36. package/dist/hub/server/index.js +5 -0
  37. package/dist/hub/server/node/index.d.ts +218 -0
  38. package/dist/hub/server/node/index.js +2 -0
  39. package/dist/hub/server/transit.d.ts +2 -0
  40. package/dist/hub/server/transit.js +2 -0
  41. package/dist/index.d.ts +512 -0
  42. package/dist/index.js +309 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/serve.d.ts +14 -0
  45. package/dist/serve.js +31 -0
  46. package/dist/serve.js.map +1 -0
  47. package/dist/spawn.d.ts +12 -0
  48. package/dist/spawn.js +25 -0
  49. package/dist/spawn.js.map +1 -0
  50. package/package.json +83 -0
@@ -0,0 +1,1560 @@
1
+ import { o as NodeTransit, s as NodeTransitObserver } from "./nodeTransit-cmtZgdpW.js";
2
+ import { Capability, ChannelTransport, IMessageTransport, Identity, JsonRpcMessage, JsonValue, LinkRpcConnection, ManagedIdentityStorageBackend, MessageWithCtx, Pattern, Permission, PrincipalId, RequestId, RootPrincipalSet, SignedCapability, SigningIdentity } from "@hediet/linkrpc";
3
+ import { ITransportServer, ServiceId, Transport } from "@hediet/linkrpc/hub/common";
4
+ import { NodeInfo, ParticipantDescriptorSource, TopologyGraph, TopologyIdGenerator, TopologyTransportInfo, TrafficEvent, TrafficSubscription, TrafficSubscriptionOptions, TrafficTransitEvent } from "@hediet/linkrpc/inspection";
5
+ //#region src/hub/server/routing/routingTopology.d.ts
6
+ interface ManagedRoutingTopology {
7
+ readonly node: TopologyGraph['nodes'][number];
8
+ readonly hubLinks: readonly {
9
+ readonly hubPortId: string;
10
+ readonly nodePortId: string;
11
+ readonly label?: string;
12
+ }[];
13
+ readonly peerPortId: string;
14
+ readonly peerLinkLabel?: string;
15
+ readonly peerTransport?: TopologyTransportInfo;
16
+ readonly adjacentNodes?: TopologyGraph['nodes'];
17
+ readonly adjacentLinks?: TopologyGraph['links'];
18
+ }
19
+ interface ManagedTopologyFragment {
20
+ readonly nodes: TopologyGraph['nodes'];
21
+ readonly links?: TopologyGraph['links'];
22
+ readonly routes?: TopologyGraph['routes'];
23
+ }
24
+ //#endregion
25
+ //#region src/hub/server/routing/routingHub.d.ts
26
+ /**
27
+ * Diagnostics sink for a {@link Hub}. Deliberately structurally compatible
28
+ * with VS Code's `LogOutputChannel`, so an extension can pass its log channel
29
+ * straight in without an adapter.
30
+ *
31
+ * The hub only ever logs *routing decisions* — method names, the resolution
32
+ * kind, claimed prefixes and error reasons — never message **payloads**
33
+ * (params/results), so attaching a logger never leaks call arguments.
34
+ */
35
+ interface IHubLogger {
36
+ trace(message: string): void;
37
+ debug(message: string): void;
38
+ info(message: string): void;
39
+ warn(message: string): void;
40
+ error(message: string): void;
41
+ }
42
+ interface HubOptions {
43
+ /** Name used in error payloads / diagnostics. */
44
+ readonly debugName?: string;
45
+ /** Optional diagnostic descriptor for this routing node. */
46
+ readonly descriptors?: readonly ParticipantDescriptorSource[];
47
+ /** Stable topology identity for this hub. A random id is generated by default. */
48
+ readonly nodeId?: string;
49
+ /** Topology node/port ID generator. Defaults to random UUIDs; never used for security nonces. */
50
+ readonly generateTopologyId?: TopologyIdGenerator;
51
+ /**
52
+ * Optional diagnostics sink. When omitted the hub logs nothing and pays no
53
+ * string-formatting cost (log calls short-circuit on the missing logger).
54
+ */
55
+ readonly logger?: IHubLogger;
56
+ /**
57
+ * Per-request idle timeout in milliseconds. A forwarded request whose
58
+ * stream is idle for this long (no response, no stream message, no
59
+ * keepalive ping) is cancelled toward its target and failed back to its
60
+ * origin with a `requestTimeout` error. Defaults to 30 minutes. Set to
61
+ * `0` (or a negative value) to disable — only sensible in tests, since
62
+ * it removes the hub's bound on `_pending` growth.
63
+ */
64
+ readonly idleTimeoutMs?: number;
65
+ /** Positive deadline per automatic peer probe, in ms (default 1000). Failures retry in the background. */
66
+ readonly peerIdentificationTimeoutMs?: number;
67
+ /**
68
+ * Optional initial node-transit observer. Additional observers can be
69
+ * attached dynamically with {@link Hub.observeTransits}. When set, the hub emits a
70
+ * {@link NodeTransit} for every message it routes (requests, responses,
71
+ * notifications, stream frames). Off by default — when omitted the emit
72
+ * sites short-circuit and allocate nothing. Never carries more than the
73
+ * method/params/result the hub already handles.
74
+ */
75
+ readonly onTransit?: NodeTransitObserver;
76
+ }
77
+ /** Minimal disposable handle; calling `dispose` detaches the link. */
78
+ interface IDisposable {
79
+ dispose(): void;
80
+ }
81
+ /** A snapshot row from {@link Hub.pendingRequests}: one in-flight forward. */
82
+ interface PendingRequestInfo {
83
+ /** Hub-rewritten id the target sees. */
84
+ readonly hubId: string;
85
+ /** Original id the origin used. */
86
+ readonly originalId: RequestId;
87
+ /** Fully-qualified method. */
88
+ readonly method: string;
89
+ /** Edge the request arrived on. */
90
+ readonly originEdgeId: string;
91
+ /** Edge the request was forwarded to. */
92
+ readonly targetEdgeId: string;
93
+ /** Milliseconds the request has been in flight. */
94
+ readonly ageMs: number;
95
+ }
96
+ /**
97
+ * Handle returned by {@link Hub.attach}. Beyond detaching the link, it lets
98
+ * the caller claim/release prefixes *bound to this specific link* without
99
+ * holding a reference to the link itself — the seam a {@link RootOverlay}
100
+ * uses to let its root services claim upstream on the participant's behalf.
101
+ */
102
+ interface AttachedLink extends IDisposable {
103
+ /** Make this link the hub's root handler. Throws if another link already holds the role. */
104
+ setAsLoopback(): void;
105
+ /** Bind `prefix` to this link (see {@link Hub.claimPrefix}). */
106
+ addPrefixRoute(prefix: ServiceId): void;
107
+ /** Release `prefix` from the table. Returns `true` if it existed. */
108
+ releasePrefix(prefix: ServiceId): boolean;
109
+ /**
110
+ * The stable inspection id the hub uses for this link in its
111
+ * {@link NodeTransit} events. A neighbouring node (e.g. an
112
+ * {@link import('./overlaySplitter').OverlaySplitter}) should label the
113
+ * *same physical edge* with this id so a {@link TransitAggregator} chains
114
+ * the two nodes' transits across the boundary.
115
+ */
116
+ readonly edgeId: string;
117
+ /** Stable topology port identity, independent of the mutable friendly edge label. */
118
+ readonly portId: string;
119
+ /** Await or retry peer discovery. Discovery also runs automatically after attachment. */
120
+ identifyPeer(): Promise<NodeInfo>;
121
+ /** Send a bounded root request to the far end while the Hub owns dispatch. */
122
+ request(method: string, params: JsonValue, timeoutMs?: number): Promise<JsonValue | undefined>;
123
+ }
124
+ interface AttachLinkOptions {
125
+ readonly edgeId?: string;
126
+ readonly transport?: TopologyTransportInfo;
127
+ /** Make this link the sole hub-root handler. Throws if another link already holds the role. */
128
+ readonly exclusiveHubRootHandler?: boolean;
129
+ /** Initial prefix routes. All must be valid, distinct, and unclaimed. */
130
+ readonly routePrefixes?: readonly ServiceId[];
131
+ }
132
+ /**
133
+ * A **Hub** is the entire routing concept of linkrpc v2 — the only noun.
134
+ * It is a tiny message router with an unauthenticated topology-correlation
135
+ * identity and exactly three moving
136
+ * parts, all expressed as {@link IMessageTransport} links:
137
+ *
138
+ * - **forwarding table** — longest-prefix serviceId → link map (the
139
+ * downstream neighbours / claimed subnets).
140
+ * - **loopback** — an optional link to the local root services (`bare`/
141
+ * `interface`-form calls land here and *never* leave the hub). This is
142
+ * the routing equivalent of `127.0.0.1`.
143
+ * - **uplink** — an optional default route. A hub *with* an uplink is also
144
+ * a participant of its parent hub, which is how hubs **nest** (a VS Code
145
+ * window hub uplinks to an OS hub uplinks to a home-server hub).
146
+ *
147
+ * Routing is pure longest-prefix forwarding with JSON-RPC id rewriting:
148
+ * a forwarded request keeps its full method name verbatim; only its `id`
149
+ * is swapped for a hub-local one so responses can be demultiplexed back to
150
+ * the originating link. There is no notion of identity, capabilities, or
151
+ * trust at this layer — those are policies layered on top.
152
+ *
153
+ * The single privileged write seam is {@link claimPrefix}; everything else
154
+ * is mechanical message shuffling.
155
+ */
156
+ declare class Hub {
157
+ private readonly _options;
158
+ /** Unauthenticated topology-correlation identity for this hub. */
159
+ readonly nodeId: string;
160
+ private readonly _table;
161
+ private readonly _links;
162
+ private readonly _pending;
163
+ private _uplink;
164
+ private _loopback;
165
+ private _nextId;
166
+ private readonly _log;
167
+ private readonly _transitObservers;
168
+ /**
169
+ * Listeners notified whenever the forwarding table changes (a prefix is
170
+ * claimed or released, or a link detaches dropping its claims). Coarse, by
171
+ * design: it carries no payload — consumers re-query the directory. This is
172
+ * the "poll now" nudge that backs `hubrpc.directory::watch`.
173
+ */
174
+ private readonly _routingListeners;
175
+ private readonly _topologyListeners;
176
+ /** Stable inspection label per link; assigned lazily / by prefix claim. */
177
+ private readonly _edgeIds;
178
+ private readonly _transportInfo;
179
+ private _nextEdgeId;
180
+ private readonly _topology;
181
+ private readonly _participantRoots;
182
+ /** Per-request idle timeout in ms; `<= 0` disables. See {@link HubOptions.idleTimeoutMs}. */
183
+ private readonly _idleMs;
184
+ constructor(_options?: HubOptions);
185
+ /** Prefix for log lines; identifies this hub when nested. */
186
+ private _tag;
187
+ /** Stable inspection label for a link; auto-assigned on first use. */
188
+ private _edgeId;
189
+ /** Give `link` a friendly inspection label (prefix / loopback / uplink). */
190
+ private _labelEdge;
191
+ /** Emit a pre-built node transit. Callers guard construction by observer count. */
192
+ private _emit;
193
+ private _transitEndpoint;
194
+ /** Number of live transit observers. */
195
+ get transitObserverCount(): number;
196
+ /** Observe routed messages until the returned handle is disposed. */
197
+ observeTransits(observer: NodeTransitObserver): IDisposable;
198
+ /**
199
+ * Publish a transit from a routing node managed by this Hub. Callers should
200
+ * check {@link transitObserverCount} before constructing the transit.
201
+ */
202
+ publishManagedTransit(transit: NodeTransit): void;
203
+ /** Replace an attached link's direct topology edge with a managed routing node. */
204
+ registerManagedRoutingTopology(attached: AttachedLink, topology: ManagedRoutingTopology): IDisposable;
205
+ /** Add an in-process endpoint or routing fragment to this Hub's topology. */
206
+ registerManagedTopologyFragment(fragment: ManagedTopologyFragment): IDisposable;
207
+ /**
208
+ * Subscribe to coarse routing-table changes (prefix claim/release/detach).
209
+ * The listener receives no arguments — it is a "something changed; re-query"
210
+ * nudge. Returns an unsubscribe function.
211
+ */
212
+ onDidChangeRouting(listener: () => void): () => void;
213
+ /** Subscribe to topology changes, including routing and background peer discovery. */
214
+ onDidChangeTopology(listener: () => void): () => void;
215
+ /** Number of active routing invalidation observers. */
216
+ get routingObserverCount(): number;
217
+ get topologyObserverCount(): number;
218
+ /** Fire all routing-change listeners. Best-effort; one throwing listener does not block the rest. */
219
+ private _fireRoutingChanged;
220
+ private _fireTopologyChanged;
221
+ private _notifyListeners;
222
+ /** Emit a stream transit with the owning request's correlated endpoints. */
223
+ private _emitStream;
224
+ /**
225
+ * Snapshot the requests currently in flight through this hub. Each row is
226
+ * one {@link _pending} entry — the authoritative in-flight set — annotated
227
+ * with its method, the edges it bridges, and how long it has waited. Useful
228
+ * for surfacing stuck/slow requests in an inspector.
229
+ */
230
+ pendingRequests(): PendingRequestInfo[];
231
+ /**
232
+ * Begin routing the messages of `link`. The hub installs itself as the
233
+ * link's listener; do not also attach another listener. Returns a
234
+ * disposable that stops routing the link (detaching the listener and
235
+ * dropping any forwarding entries, uplink/loopback slots, and pending
236
+ * responses bound to it) when disposed. Peer discovery starts in the
237
+ * background; unresolved or unsupported peers never block routing.
238
+ */
239
+ attach(link: IMessageTransport, opts?: AttachLinkOptions): AttachedLink;
240
+ /**
241
+ * Attach a fresh in-memory link and hand back its far end alongside the
242
+ * {@link AttachedLink} handle. Creates a {@link TransportPair} internally,
243
+ * {@link attach}es the near side to the hub, and returns the attach handle
244
+ * augmented with `transport` — the far side for a peer (a consumer
245
+ * connection, an in-process participant, …) to drive. Equivalent to
246
+ * `const p = new TransportPair(); const link = hub.attach(p.a);
247
+ * return { ...link, transport: p.b };`. Disposing the returned link also
248
+ * disposes `transport`.
249
+ */
250
+ attachOut(opts?: AttachLinkOptions): AttachedLink & {
251
+ readonly transport: IMessageTransport;
252
+ };
253
+ private _detach;
254
+ /** Set (or clear) the default route. Automatically attaches the link. */
255
+ setUplink(link: IMessageTransport | undefined): void;
256
+ /** Set (or clear) the root handler. Clear the existing handler before assigning a different link. */
257
+ setLoopback(link: IMessageTransport | undefined): void;
258
+ /**
259
+ * Bind a serviceId `prefix` to `link`. Like initial attachment routes,
260
+ * this is a privileged operation; higher layers enforce who may claim.
261
+ *
262
+ * Claim-once: throws if the prefix is malformed or already owned.
263
+ */
264
+ claimPrefix(link: IMessageTransport, prefix: ServiceId): void;
265
+ private _validatePrefixClaim;
266
+ private _claimPrefix;
267
+ releasePrefix(prefix: ServiceId): boolean;
268
+ /** Snapshot of currently-claimed prefixes. */
269
+ claimedPrefixes(): ServiceId[];
270
+ /**
271
+ * Trust the inspection interfaces mounted for `serviceId` on this attached
272
+ * in-process link. Only exact reserved inspection interface calls are hidden.
273
+ */
274
+ markInspectionService(attached: AttachedLink, serviceId: ServiceId): void;
275
+ /** Build a topology snapshot on demand for the addressed observer service. */
276
+ getTopologyGraph(observerServiceId: string): TopologyGraph;
277
+ /**
278
+ * Fan a **root-form** request out to every distinct downstream participant
279
+ * link (the claimed-prefix owners), skipping the link that owns
280
+ * `excludePrefix` (typically the hub's own services prefix) and the uplink.
281
+ * Each participant is queried **once** even if it owns several prefixes.
282
+ * Failures (unreachable participant, timeout, no root handler) are dropped,
283
+ * so the result holds one entry per link that answered.
284
+ *
285
+ * The request is delivered to each participant's **root** services — on an
286
+ * overlay uplink the splitter routes a root-form method to the participant
287
+ * (`H·root → P`), which is never forwarded and never gated. That is what
288
+ * lets the hub's global directory gather participant self-listings without
289
+ * signing or capabilities.
290
+ */
291
+ queryParticipantRoots(method: string, params: JsonValue, excludePrefix?: ServiceId): Promise<JsonValue[]>;
292
+ /**
293
+ * Keep a root-form streaming request open on every current participant.
294
+ * Routing changes reconcile the participant set; application stream payloads
295
+ * are collapsed into a coarse callback so callers can re-query.
296
+ */
297
+ watchParticipantRoots(method: string, params: JsonValue, onTick: () => void, excludePrefix?: ServiceId): IDisposable;
298
+ /**
299
+ * Send a single request down `link` and resolve with its result — the
300
+ * hub-originated analogue of {@link _routeRequest}. It allocates a hub id,
301
+ * parks a {@link _pending} entry whose `origin` is a tiny in-memory sink
302
+ * that settles this promise on the matching response, and writes the
303
+ * request to `link`. It therefore reuses the full demux / idle-timeout /
304
+ * detach machinery: a response routes back through {@link _demux} into the
305
+ * sink, an idle target fails it via {@link _onIdleTimeout}, and a detaching
306
+ * target fails it via {@link _detach}.
307
+ */
308
+ private _requestOnLink;
309
+ private _watchOnLink;
310
+ /**
311
+ * The link a still-in-flight forwarded request originally arrived on,
312
+ * keyed by the **hub-rewritten** id the request now carries (i.e. the
313
+ * `id` a target sees on its delivered message). Valid from the moment the
314
+ * hub forwards the request until its response is demultiplexed; returns
315
+ * `undefined` for unknown / already-answered ids.
316
+ *
317
+ * This is the seam a hub-hosted participant uses to learn *which* link a
318
+ * call it is handling came from, so it can bind a prefix claim to that
319
+ * exact link via {@link claimPrefix} without the routing core ever
320
+ * growing a notion of authenticated identity.
321
+ */
322
+ getSourceTransport(requestId: RequestId): IMessageTransport | undefined;
323
+ private _onMessage;
324
+ private _resolve;
325
+ private _routeRequest;
326
+ private _routeNotification;
327
+ private _demux;
328
+ /**
329
+ * Route a `$stream::send` notification. Unlike an ordinary notification, a
330
+ * stream message is **correlated to an in-flight request by `requestId`**,
331
+ * not addressed by method name — so it follows the same path the request
332
+ * established (recorded in {@link _pending}), exactly like a
333
+ * {@link _demux | response}, and the hub rewrites `requestId` across the
334
+ * forwarding boundary the same way it rewrites a response `id`.
335
+ *
336
+ * The message's explicit `dir` picks the traversal — no inference from the
337
+ * arriving link:
338
+ *
339
+ * - **`toCaller`** (callee → caller progress): routed like a response. The
340
+ * streamer used the hub-rewritten id it *received*, so `requestId` is a
341
+ * pending hub id; only the link the request was forwarded to may stream
342
+ * on it. Restore the origin's original id and forward to the origin.
343
+ * - **`toCallee`** (caller → callee input / cancel / ping): the reverse
344
+ * traversal of the same entry. The streamer used the original id it
345
+ * *sent*; rewrite `requestId` to the hub id the target was handed and
346
+ * forward to the target. Only the origin may stream this direction.
347
+ *
348
+ * Every routed stream message resets the request's idle timer (a keepalive
349
+ * ping is exactly a `toCallee` control with no payload). A `$stream::send`
350
+ * matching no in-flight request is dropped (late / unknown correlation).
351
+ */
352
+ private _routeStream;
353
+ /** Delete a pending entry and clear its idle timer. */
354
+ private _deletePending;
355
+ /** Arm (or re-arm) the idle timer for a pending entry. No-op when disabled. */
356
+ private _armIdle;
357
+ /** Reset the idle timer on stream activity. */
358
+ private _resetIdle;
359
+ /**
360
+ * A request went idle past {@link _idleMs}. Cancel the (presumably hung)
361
+ * callee, fail the caller with a `requestTimeout`, and drop the entry —
362
+ * the bound that keeps `_pending` from growing without limit.
363
+ */
364
+ private _onIdleTimeout;
365
+ /**
366
+ * Author a `toCallee` cancel toward `target` for the request the target
367
+ * knows as `hubId`. Used on caller-disconnect and idle-timeout — the hub
368
+ * itself originates the message, which is why `dir` is explicit on the
369
+ * wire (there is no inbound link to infer it from).
370
+ */
371
+ private _injectCancel;
372
+ private _noTargetCode;
373
+ private _noTargetMessage;
374
+ private _replyError;
375
+ }
376
+ //#endregion
377
+ //#region src/hub/server/hubInspector.d.ts
378
+ interface HubTrafficWatchOptions extends TrafficSubscriptionOptions {}
379
+ interface HubTrafficSubscription extends IDisposable, TrafficSubscription {}
380
+ interface HubTrafficSource {
381
+ observe(observer: (transit: TrafficTransitEvent) => void): IDisposable;
382
+ }
383
+ /** Publishes dynamically observed raw node transits through bounded traffic streams. */
384
+ declare class HubInspector implements IDisposable {
385
+ private readonly _hub;
386
+ private readonly _subscribers;
387
+ private readonly _trafficSources;
388
+ private readonly _trafficWatches;
389
+ private readonly _observation;
390
+ constructor(_hub: Hub);
391
+ get observerCount(): number;
392
+ subscribe(options: HubTrafficWatchOptions, send: (event: TrafficEvent) => Promise<void>): HubTrafficSubscription;
393
+ dispose(): void;
394
+ /** Add an endpoint traffic source managed by this Hub inspection service. */
395
+ addTrafficSource(source: HubTrafficSource): IDisposable;
396
+ private _startTrafficSources;
397
+ private _startTrafficSource;
398
+ private _stopTrafficSources;
399
+ private _onTransit;
400
+ private _emitTransit;
401
+ }
402
+ //#endregion
403
+ //#region src/hub/server/routing/overlaySplitter.d.ts
404
+ /**
405
+ * Inspection wiring for an {@link OverlaySplitter}. When supplied, the splitter
406
+ * emits a {@link NodeTransit} for every message it routes. The three port edge
407
+ * ids name the splitter's own edges; **`edges.h` must equal the parent hub
408
+ * link's `edgeId`** so a {@link TransitAggregator} chains the splitter's and
409
+ * hub's transits across the shared boundary (the splitter forwards request ids
410
+ * verbatim there, so the `(edgeId, requestId)` endpoints line up).
411
+ */
412
+ interface OverlaySplitterInspection {
413
+ readonly nodeId: string;
414
+ readonly onTransit: NodeTransitObserver;
415
+ readonly edges: {
416
+ readonly p: string;
417
+ readonly c: string;
418
+ readonly h: string;
419
+ };
420
+ }
421
+ /**
422
+ * A fixed three-port message splitter — the per-participant primitive that
423
+ * replaces a nested {@link import('./routingHub').Hub} inside a
424
+ * {@link import('./rootOverlay').RootOverlay}.
425
+ *
426
+ * Ports:
427
+ * - **P** — the participant (downstream connection),
428
+ * - **C** — the local root-services connection (`hubServiceIdRegistry::registerServiceId`,
429
+ * `hubrpc.directory`, `identity::*`, …),
430
+ * - **H** — the uplink to the parent hub.
431
+ *
432
+ * Routing rules (verbatim to the design):
433
+ * ```
434
+ * P·root → C P·* → H
435
+ * H·* → P H·root → P
436
+ * C·* → P
437
+ * ```
438
+ * where `root` means a bare/interface-form method and `*` a fully-qualified
439
+ * (`serviceId::…`) method. Responses follow the reverse path. `H·root → P`
440
+ * lets the parent hub reach the participant's *own* root services (e.g. the
441
+ * participant-served root directory) — distinct from `P·root → C`, which is
442
+ * the participant *consuming* the overlay's root services; the two are
443
+ * opposite directions served by different connections, so they never collide.
444
+ *
445
+ * ### `$stream::send` — correlated by `requestId`, not by method form
446
+ *
447
+ * A {@link STREAM_METHOD} (`$stream::send`) notification is *interface-form*
448
+ * (`$stream::send`), so the method-form rules above would naively treat it as
449
+ * `root` and send a participant's stream to **C** (and route an inbound one as
450
+ * a root call to **P**). That is wrong: a stream message belongs to an in-flight
451
+ * request's lifetime and must follow **that request's** path, exactly like a
452
+ * response does. So `$stream::send` bypasses the method-form table and is
453
+ * routed like a response instead:
454
+ *
455
+ * - **P → ?**: the participant streams using the `requestId` it *observed*,
456
+ * which for a request it serves is the tag the splitter stamped on the
457
+ * delivered request id (`H\0…` / `C\0…`). We {@link _decodeId | decode} that
458
+ * tag, restore the original id, and forward to the tagged origin (H or C) —
459
+ * the same demux responses get. An **untagged** `requestId` means the
460
+ * participant *initiated* the request (P→H or P→C); we cannot tell which
461
+ * statelessly, so we default to the **uplink** (the common case: streaming
462
+ * to a fully-qualified service via the parent).
463
+ * - **H/C → P**: forwarded verbatim to the participant (never dropped), as a
464
+ * response would be — the parent has already restored P's original
465
+ * `requestId`, symmetric to response-id rewriting.
466
+ *
467
+ * PARTICIPANT-INITIATED STREAMS — the one stateful case: because `requestId`
468
+ * lives in `params` (not a structural top-level id the splitter rewrites in
469
+ * lock-step), a stream frame for a request the participant *initiated* (`P→C`
470
+ * or `P→H`) carries P's *own untagged* id, which alone cannot say whether the
471
+ * call went to the root (C) or the uplink (H). The splitter therefore keeps a
472
+ * small `requestId → target` map ({@link _pInitiated}), populated when P sends
473
+ * such a request and cleared when its response returns, and consults it to
474
+ * route those frames. This is the splitter's *only* per-request state; every
475
+ * other path still routes statelessly via id-tagging.
476
+ *
477
+ * ### Stateless demux via id-tagging
478
+ *
479
+ * P is the only port that receives requests from *two* sources (H and C),
480
+ * so a response from P is ambiguous and ids can collide (`H` and `C` may
481
+ * each send `id:5`). Rather than a pending-request map (with its attendant
482
+ * timeout/leak problem), the splitter rewrites **only requests destined for
483
+ * P**, prepending the origin and original id type to the id:
484
+ *
485
+ * ```
486
+ * H→P: id' = "H\0n\042" (push origin tag)
487
+ * C→P: id' = "C\0s\0abc"
488
+ * P→?: decode id' → route to H or C, restore the original id
489
+ * ```
490
+ *
491
+ * Every other edge (P→H, P→C and the responses coming back from H/C to P)
492
+ * passes **verbatim** — those targets each have a single peer, so there is no
493
+ * response ambiguity. The sole exception is *stream* frames for
494
+ * participant-initiated requests, whose target is recovered from the small
495
+ * {@link _pInitiated} map (see above); apart from that the id encodes its own
496
+ * return path.
497
+ *
498
+ * NOTE: unlike the central {@link import('./routingHub').Hub}, the tag is
499
+ * not authenticated. A misbehaving participant could emit a forged tagged
500
+ * response toward H or C; each endpoint still drops ids it has no pending
501
+ * request for, and identity/signing live in a higher layer. For the
502
+ * single-participant overlay this is an acceptable trade for being fully
503
+ * stateless. If unforgeable return paths are ever required here, HMAC the
504
+ * tag with a splitter-private secret.
505
+ */
506
+ declare class OverlaySplitter<TContext = undefined> {
507
+ private readonly _participant;
508
+ private readonly _root;
509
+ private readonly _uplink;
510
+ private _disposed;
511
+ private readonly _inspect;
512
+ /**
513
+ * Per-request routing target (`C` or `H`) for requests the participant
514
+ * *initiated*, keyed by P's own request id. Streams for those requests
515
+ * carry P's untagged id, which alone can't say whether the call went to the
516
+ * root (C) or the uplink (H); this map recovers it. Populated when P sends
517
+ * the request, cleared when its response returns. The splitter's only
518
+ * per-request state — every other path routes statelessly via id-tagging.
519
+ */
520
+ private readonly _pInitiated;
521
+ constructor(_participant: IMessageTransport<MessageWithCtx<TContext>>, _root: IMessageTransport<JsonRpcMessage, MessageWithCtx<TContext>>, _uplink: IMessageTransport, inspection?: OverlaySplitterInspection);
522
+ /** Detach all three ports. Does not dispose the transports themselves. */
523
+ dispose(): void;
524
+ /** Edge id of an upstream origin port. */
525
+ private _originEdge;
526
+ /** Emit a node transit if inspection is enabled. Zero-cost otherwise. */
527
+ private _emit;
528
+ private _onFromParticipant;
529
+ /**
530
+ * Route a participant-emitted `$stream::send` by its `requestId` tag,
531
+ * mirroring how a {@link _onFromParticipant | response} is demuxed. The
532
+ * participant streams with the id it *observed*; for a request it serves
533
+ * that id carries the origin tag the splitter stamped, so decoding it
534
+ * yields the return port and the original id. Untagged → a request the
535
+ * participant *initiated*; recover its target (C or H) from {@link
536
+ * _pInitiated}, defaulting to the uplink.
537
+ */
538
+ private _routeParticipantStream;
539
+ private _onFromUpstream;
540
+ /** Build a request/notification transit (`out` absent ⇒ dropped here). */
541
+ private _mkForward;
542
+ /** Build a response transit crossing from `inEdge`/`inId` to `outEdge`/`outId`. */
543
+ private _mkResponse;
544
+ }
545
+ //#endregion
546
+ //#region src/hub/server/routing/rootOverlay.d.ts
547
+ interface RootOverlayOptions {
548
+ /**
549
+ * The uplink transport to the parent hub. The overlay forwards all
550
+ * prefixed (`serviceId::…`) participant traffic here and receives the
551
+ * parent's routed traffic back. Obtain it from `parentHub.attach(pair.b)`
552
+ * and pass `pair.a`.
553
+ */
554
+ readonly uplink: IMessageTransport;
555
+ /**
556
+ * Optional inspection wiring forwarded to the underlying
557
+ * {@link OverlaySplitter}. Set `edges.h` to the parent hub link's `edgeId`
558
+ * (from `parentHub.attach(...).edgeId`) so transits chain across the
559
+ * boundary.
560
+ */
561
+ readonly inspection?: OverlaySplitterInspection;
562
+ /** Parent-facing topology port. Defaults to a generated connection-lifetime id. */
563
+ readonly uplinkPortId?: string;
564
+ }
565
+ /**
566
+ * A **RootOverlay** is the per-participant front door. It is a pure
567
+ * {@link OverlaySplitter} wiring with no routing table, no nested hub, and —
568
+ * deliberately — **no knowledge of which services it serves**. It exposes:
569
+ *
570
+ * - {@link root} — the connection serving this participant's root services.
571
+ * Install bootstrap/identity modules onto it with `registerHubServices` /
572
+ * `registerIdentityServices` *before or after* connecting the participant.
573
+ * - {@link connectParticipant} — wires the downstream participant through the
574
+ * splitter (`P·root → root`, `P·* → uplink`, `uplink·* → P`).
575
+ *
576
+ * Because an overlay represents exactly one participant, prefix claims happen
577
+ * only on the parent (via the {@link import('./routingHub').AttachedLink} that
578
+ * `registerHubServices` was given); there is no local table to write.
579
+ *
580
+ * The overlay is generic over the per-call `TContext` its {@link root}
581
+ * connection sees. Pass a context-stamping {@link ChannelTransport} to
582
+ * {@link connectParticipant} to attach per-call context. Most callers connect a
583
+ * plain (or signature-verifying) transport and leave `TContext` as `undefined`
584
+ * (context-free).
585
+ */
586
+ declare class RootOverlay<TContext = undefined> {
587
+ /**
588
+ * The per-participant root connection. Register the overlay's root
589
+ * services here — e.g. `registerHubServices(overlay.root, upstream)` and
590
+ * `registerIdentityServices(overlay.root, { resolveIdentity })`. Reachable
591
+ * only by this overlay's participant (root-addressed calls land here).
592
+ *
593
+ * Its inbound transport carries the overlay's `TContext`, so identity-aware
594
+ * front doors — notably the consent `hubAccess` service — can be served here
595
+ * for the interface-form calls a participant addresses to its own root.
596
+ */
597
+ readonly root: LinkRpcConnection<TContext>;
598
+ readonly nodeId: string;
599
+ readonly participantPortId: string;
600
+ readonly rootPortId: string;
601
+ readonly rootNodeId: string;
602
+ readonly uplinkPortId: string;
603
+ private readonly _uplink;
604
+ private readonly _rootPair;
605
+ private readonly _inspection;
606
+ private _splitter;
607
+ private _disposed;
608
+ constructor(options: RootOverlayOptions);
609
+ managedTopology(edgeLabel?: string, peerTransport?: TopologyTransportInfo): ManagedRoutingTopology;
610
+ /**
611
+ * Connect the downstream participant. Root-addressed calls from it hit the
612
+ * {@link root} services; prefixed calls forward via the uplink. May be
613
+ * called once.
614
+ *
615
+ * Connect a context-stamping {@link ChannelTransport} when `TContext` is
616
+ * concrete; otherwise connect the raw participant transport directly.
617
+ */
618
+ connectParticipant(participant: IMessageTransport<MessageWithCtx<TContext>>): void;
619
+ /**
620
+ * Tear the overlay down: detach the splitter and dispose the internal root
621
+ * transport pair. The caller owns the uplink and the participant transport
622
+ * and disposes them separately (disposing the uplink's
623
+ * {@link import('./routingHub').AttachedLink} releases any claimed prefixes
624
+ * on the parent). Idempotent.
625
+ */
626
+ dispose(): void;
627
+ }
628
+ //#endregion
629
+ //#region src/hub/server/rootServices.d.ts
630
+ interface RegisterHubServicesOptions {
631
+ /**
632
+ * ServiceId under which the parent serves its global reflection services.
633
+ * The overlay's directory listing *refers* world-view queries here.
634
+ * Defaults to `'hub'`.
635
+ */
636
+ readonly hubServiceId?: string;
637
+ /**
638
+ * Absolute serviceId region this connection may claim freely (anything
639
+ * at/under it, no capability needed), surfaced verbatim through
640
+ * `hubGrantedServiceId::get` as `grantedServiceIdNamespace`. Typically the
641
+ * connection's attested identity key. Defaults to `''` — "claim nothing
642
+ * freely" — which still lets `authorizeClaim` admit claims.
643
+ */
644
+ readonly grantedServiceIdNamespace?: string;
645
+ /**
646
+ * Consulted in `hubGrantedServiceId::register` before the prefix is
647
+ * claimed. Return `{ ok: false, reason }` to deny (surfaced to the
648
+ * participant as an error). When omitted, claims default to "must be within
649
+ * `grantedServiceIdNamespace`".
650
+ */
651
+ authorizeClaim?(requestedPrefix: ServiceId): {
652
+ ok: true;
653
+ } | {
654
+ ok: false;
655
+ reason: string;
656
+ };
657
+ }
658
+ /**
659
+ * Install a participant's **connection root services** onto its overlay root
660
+ * connection `root`:
661
+ *
662
+ * - `hubGrantedServiceId::get` — the connection round-trip: reports the
663
+ * `grantedServiceIdNamespace` this connection may claim freely.
664
+ * - `hubGrantedServiceId::register` — claims a prefix on
665
+ * `upstream` (the parent hub link) within the connection's granted namespace,
666
+ * gated by `authorizeClaim` (default: namespace membership). Because an
667
+ * overlay represents a single participant, *one* upstream claim suffices: all
668
+ * prefixed traffic the parent routes to this overlay is for this participant,
669
+ * so the {@link OverlaySplitter} delivers it without any local table.
670
+ * - `hubrpc.directory::list` — by default a **referral** (own root interfaces
671
+ * plus one row pointing at `<hubServiceId>::hubrpc.directory`); an
672
+ * implementation is free to aggregate the parent's listing instead.
673
+ * - `hubrpc.schemas::get` — schemas for this overlay's own root interfaces.
674
+ *
675
+ * The hub's consent front door (`hubAccess::*`) is installed separately at the
676
+ * overlay root by {@link registerHubAccessService}; it needs no capability of
677
+ * its own because the root is never forwarded.
678
+ *
679
+ * The overlay itself stays unaware of which services it serves; this is the
680
+ * routing module that gives it its front door.
681
+ */
682
+ declare function registerHubServices(root: LinkRpcConnection<unknown>, upstream: AttachedLink, options?: RegisterHubServicesOptions): void;
683
+ interface RegisterIdentityServicesOptions {
684
+ /**
685
+ * Resolve the managed identity for this participant. Called **lazily** —
686
+ * only when the participant first invokes `identity::*` — so registering
687
+ * the service does not mint or load any key material (matches the consent
688
+ * model: serving `identity::*` costs nothing until the participant opts in).
689
+ */
690
+ resolveIdentity(): Promise<Identity>;
691
+ /**
692
+ * Optional per-identity persistent key/value store, registered as
693
+ * `identity.storage::*` alongside `identity::*`.
694
+ */
695
+ storage?: ManagedIdentityStorageBackend;
696
+ }
697
+ /**
698
+ * Install `identity::*` (and optionally `identity.storage::*`) onto a
699
+ * participant's overlay root connection. The identity is resolved lazily on
700
+ * first use — see {@link registerLazyIdentityOnOverlay}.
701
+ *
702
+ * This is the identity counterpart to {@link registerHubServices}: a small,
703
+ * composable module the overlay does not need to know about.
704
+ */
705
+ declare function registerIdentityServices(root: LinkRpcConnection<unknown>, options: RegisterIdentityServicesOptions): void;
706
+ //#endregion
707
+ //#region src/hub/server/hubRegister.d.ts
708
+ /**
709
+ * Out-of-band, per-call context produced by {@link withRequestIdContext}. A
710
+ * typed handler only ever sees the zod-stripped params (the `$hubrpc` envelope
711
+ * and the wire id are gone), so the originating request's wire id is smuggled
712
+ * alongside the message via the channel's `context` slot — never over a wire.
713
+ *
714
+ * The register handler uses it to resolve the link a forwarded claim arrived on
715
+ * via {@link Hub.getSourceTransport}.
716
+ */
717
+ interface RegisterCallContext {
718
+ /**
719
+ * The hub-rewritten wire id of the inbound request, or `undefined` for
720
+ * responses / notifications. Keyed lookups into
721
+ * {@link Hub.getSourceTransport} use exactly this id.
722
+ */
723
+ readonly requestId: RequestId | undefined;
724
+ }
725
+ /**
726
+ * Annotate an inbound transport so every request carries its wire id in a
727
+ * {@link RegisterCallContext}. Synchronous and signature-agnostic: it neither
728
+ * verifies signatures nor checks capabilities (that has already happened on the
729
+ * forwarded path — see {@link registerHubServiceIdRegistry}). It exists only so
730
+ * a hub-hosted handler can recover the request id the typed channel would
731
+ * otherwise strip.
732
+ */
733
+ declare function withRequestIdContext(link: IMessageTransport): ChannelTransport<RegisterCallContext>;
734
+ interface HubRegisterOptions {
735
+ /** The central hub whose forwarding-table claims are written. */
736
+ readonly hub: Hub;
737
+ /** ServiceId the register endpoint (and the rest of the hub services) is mounted under. */
738
+ readonly hubServiceId: string;
739
+ }
740
+ /**
741
+ * Install the hub's privileged **claim front door** — the typed
742
+ * `hubServiceIdRegistry::registerServiceId` handler — onto the hub services
743
+ * `connection` (mounted under `hubServiceId`).
744
+ *
745
+ * Unlike the per-overlay `registerHubServices` front door — which only lets a
746
+ * participant claim *within its provenance-granted namespace* — this endpoint
747
+ * is the path for claiming a prefix **outside** that namespace.
748
+ *
749
+ * The handler is a **pure side effect**. Authentication (signature) and
750
+ * authorization (an admin-rooted capability that permits
751
+ * `hub::hubServiceIdRegistry::registerServiceId` for the requested
752
+ * `requestedPrefix`) are enforced *before* the call ever reaches here, by the
753
+ * {@link import('./forwardedCallGate').withForwardedCallGate forwarded-call
754
+ * gate} every untrusted participant sits behind. By the time the request lands,
755
+ * it is already known to be authentic and authorized, so the handler only:
756
+ *
757
+ * 1. validates the prefix is well-formed;
758
+ * 2. resolves the link the request was forwarded from via
759
+ * {@link Hub.getSourceTransport} (keyed by {@link RegisterCallContext.requestId});
760
+ * and
761
+ * 3. binds the prefix to that link via {@link Hub.claimPrefix}.
762
+ *
763
+ * The source transport answers only *where* to route the prefix, never
764
+ * *whether* the claim is allowed.
765
+ */
766
+ declare function registerHubServiceIdRegistry(connection: LinkRpcConnection<RegisterCallContext>, options: HubRegisterOptions): void;
767
+ //#endregion
768
+ //#region src/hub/server/hubServices.d.ts
769
+ interface HubServicesOptions {
770
+ /** ServiceId the global services are mounted under. Defaults to `'hub'`. */
771
+ readonly hubServiceId?: string;
772
+ /** Optional diagnostic descriptor for the hub participant. */
773
+ readonly descriptors?: readonly ParticipantDescriptorSource[];
774
+ /** Runs before serving a global directory list or starting a directory watch. */
775
+ readonly beforeDirectoryQuery?: () => void | Promise<void>;
776
+ }
777
+ interface HubServices {
778
+ /** The in-process connection backing the hub's global services. */
779
+ readonly connection: LinkRpcConnection<RegisterCallContext>;
780
+ /** ServiceId the services are mounted under. */
781
+ readonly hubServiceId: string;
782
+ /** Traffic/topology inspector backing the hub's inspection services. */
783
+ readonly inspector: HubInspector;
784
+ /** Close the connection and detach the in-process link from the hub. */
785
+ dispose(): void;
786
+ }
787
+ declare function createHubServiceInterfaces(hub: Hub, options?: HubServicesOptions): HubServices;
788
+ //#endregion
789
+ //#region src/hub/server/accessCandidates.d.ts
790
+ /** A single entry of the aggregated interface directory. */
791
+ interface DirectoryEntry {
792
+ readonly serviceId: string;
793
+ readonly interfaceId: string;
794
+ readonly hash: string;
795
+ readonly serviceDescription?: string;
796
+ readonly rootPrincipalSets?: readonly RootPrincipalSet[];
797
+ }
798
+ interface AccessInterfaceRequirement {
799
+ readonly id: string;
800
+ /** Optional schema-version pin; when set the candidate's hash must match. */
801
+ readonly hash?: string;
802
+ /** Defaults to true. */
803
+ readonly required: boolean;
804
+ }
805
+ interface AccessMemberRequirement {
806
+ readonly interfaceId: string;
807
+ /** Member name matcher (`{ exact }` or `{ prefix }`). */
808
+ readonly member: Pattern;
809
+ /** Defaults to true. */
810
+ readonly required: boolean;
811
+ }
812
+ interface AccessSlotRequest {
813
+ readonly interfaces: readonly AccessInterfaceRequirement[];
814
+ readonly members: readonly AccessMemberRequirement[];
815
+ }
816
+ interface AccessSlotCandidate {
817
+ readonly serviceId: string;
818
+ readonly serviceDescription?: string;
819
+ /** Subset of the slot's requested interfaces actually present on this candidate. */
820
+ readonly satisfiedInterfaces: readonly AccessInterfaceRequirement[];
821
+ /**
822
+ * Requested interfaces the candidate does NOT satisfy. By construction
823
+ * every entry here is `required: false` — a required-but-missing
824
+ * interface filters the candidate out before it is emitted.
825
+ */
826
+ readonly unsatisfiedInterfaces: readonly AccessInterfaceRequirement[];
827
+ }
828
+ interface ResolvedAccessSlot {
829
+ readonly request: AccessSlotRequest;
830
+ /** Empty = no service satisfies every required interface. */
831
+ readonly candidates: readonly AccessSlotCandidate[];
832
+ }
833
+ interface ResolvedCandidates {
834
+ readonly dependencies: Readonly<Record<string, ResolvedAccessSlot>>;
835
+ /** Slot ids for which no candidate satisfied every required interface. */
836
+ readonly noCandidateSlots: readonly string[];
837
+ }
838
+ /**
839
+ * Fetch the full interface directory by recursively walking the hub's
840
+ * **referral** directory graph. {@link walkHubDetailed} follows explicit
841
+ * `hubrpc.directory` listings to collect leaf interfaces (folding transitive
842
+ * root-node-id requirements onto descendants along the way). Routing claims do
843
+ * not create referrals. This is the v2 replacement for the hub-internal
844
+ * `_fullDirectory()`.
845
+ */
846
+ declare function fetchFullDirectory(connection: LinkRpcConnection, hubServiceId?: string): Promise<DirectoryEntry[]>;
847
+ /**
848
+ * Resolve every dependency slot against a directory snapshot. Pure port of
849
+ * the v1 hub's per-request candidate resolution loop + `_candidatesForSlot`.
850
+ */
851
+ declare function resolveAccessCandidates(slots: Readonly<Record<string, AccessSlotRequest>>, directory: readonly DirectoryEntry[]): ResolvedCandidates;
852
+ /**
853
+ * Match one slot against the directory snapshot. A service is a candidate iff
854
+ * it satisfies every `required` interface (by id, and by hash when pinned).
855
+ */
856
+ declare function candidatesForSlot(slot: AccessSlotRequest, directory: readonly DirectoryEntry[]): AccessSlotCandidate[];
857
+ //#endregion
858
+ //#region src/hub/server/capabilityProposal.d.ts
859
+ /**
860
+ * Hub-minted "proposal" of a capability the user is about to approve.
861
+ * `capability` is the readable JSON the consent UI renders; `$hubData` is an
862
+ * opaque blob the UI must round-trip verbatim — only the issuer inspects it.
863
+ */
864
+ interface CapabilityProposal {
865
+ readonly capability: Capability;
866
+ /** Opaque issuer-private signed blob; round-trip verbatim. */
867
+ readonly $hubData: unknown;
868
+ }
869
+ /**
870
+ * Access-grant duration vocabulary.
871
+ *
872
+ * `once` and `shortLived` share the same 5-minute TTL — they differ only on
873
+ * the *use* axis: `once` is the single-use "Allow once" choice (the consent
874
+ * host additionally pins a `callBind`, making it intrinsically single-use),
875
+ * while `shortLived` is an ordinary multi-use grant that simply expires soon.
876
+ * `persistent` never expires (no `expiresAtMs`); revocation is the only way to
877
+ * end it.
878
+ */
879
+ type AccessDurationName = 'once' | 'shortLived' | 'longLived' | 'persistent';
880
+ /**
881
+ * Convert an access duration into a Unix-milliseconds expiration timestamp, so
882
+ * handlers compute the same `expiresAtMs` the issuer mints with. Returns
883
+ * `undefined` for `persistent` (and any future never-expiring duration), which
884
+ * callers pass straight to {@link CapabilityProposalIssuer.propose}/`mint` —
885
+ * both omit `expiresAtMs` when it is `undefined`, yielding a non-expiring cap.
886
+ * Defaults to the safe short TTL (`shortLived`) when no duration is supplied.
887
+ */
888
+ declare function durationToExp(duration: AccessDurationName | undefined): number | undefined;
889
+ interface ProposeArgs {
890
+ readonly audience: PrincipalId;
891
+ readonly permissions: readonly Permission[];
892
+ /** Unix milliseconds. Absent = never expires. */
893
+ readonly expiresAtMs?: number;
894
+ }
895
+ /**
896
+ * Issues capabilities and capability proposals signed by an admin
897
+ * {@link SigningIdentity}. One instance per hub host; tracks redeemed nonces
898
+ * for one-shot proposal redemption.
899
+ */
900
+ declare class CapabilityProposalIssuer {
901
+ private readonly _issuer;
902
+ private readonly _redeemedNonces;
903
+ private _nonceCounter;
904
+ constructor(_issuer: SigningIdentity);
905
+ get issuerPrincipalId(): PrincipalId;
906
+ /**
907
+ * Build an unsigned `Capability` the issuer is willing to sign, tagged with
908
+ * an internal marker. {@link signProposal} refuses to sign anything lacking
909
+ * the marker — a guard against a handler fabricating arbitrary caps. The
910
+ * marker is non-enumerable, so it never affects serialization or signing.
911
+ */
912
+ propose(args: ProposeArgs): Capability;
913
+ /**
914
+ * Mint a final `SignedCapability` directly from a proposed cap, bypassing
915
+ * the preview round-trip. Use when no UI needs to render the unsigned form.
916
+ */
917
+ mint(args: ProposeArgs): Promise<SignedCapability>;
918
+ /**
919
+ * Pair a proposed `Capability` with a domain-separated signature over
920
+ * `canonicalJson({ preview: cap, bind? })`. The result is **not** a bearer
921
+ * token — only this issuer can redeem it.
922
+ */
923
+ signProposal(cap: Capability, opts?: {
924
+ readonly bind?: JsonValue;
925
+ }): Promise<CapabilityProposal>;
926
+ /**
927
+ * Verify and redeem a {@link CapabilityProposal}, returning a real
928
+ * {@link SignedCapability}. Checks: `$hubData` signature, preview deep-equal
929
+ * to `proposal.capability`, optional `bind` match, and one-shot nonce.
930
+ */
931
+ redeemProposal(proposal: CapabilityProposal, opts?: {
932
+ readonly expectedBind?: JsonValue;
933
+ }): Promise<SignedCapability>;
934
+ private _sign;
935
+ private _nextNonce;
936
+ }
937
+ //#endregion
938
+ //#region src/hub/server/hubAccessService.d.ts
939
+ interface AccessConsumer {
940
+ readonly name: string;
941
+ readonly origin?: string;
942
+ readonly purpose?: string;
943
+ }
944
+ type AccessDuration = AccessDurationName;
945
+ interface AccessSlotBinding {
946
+ readonly serviceId: string;
947
+ /** Interface ids (of the slot's requested interfaces) the grant covers. */
948
+ readonly interfaces: readonly string[];
949
+ }
950
+ interface AccessRequestArgs {
951
+ readonly consumer: AccessConsumer;
952
+ /** Verified signer of the `hubAccess::request` call; the capability audience. */
953
+ readonly consumerPrincipalId: PrincipalId;
954
+ readonly dependencies: Readonly<Record<string, ResolvedAccessSlot>>;
955
+ readonly duration: AccessDuration | undefined;
956
+ }
957
+ type AccessDecision = {
958
+ readonly granted: true;
959
+ readonly resolvedSlots: Readonly<Record<string, AccessSlotBinding>>;
960
+ readonly capabilities: readonly SignedCapability[];
961
+ } | {
962
+ readonly granted: false;
963
+ readonly reason?: string;
964
+ };
965
+ interface AccessExtendMember {
966
+ readonly interfaceId: string;
967
+ readonly member: Pattern;
968
+ /** Defaults to true. */
969
+ readonly required: boolean;
970
+ }
971
+ interface AccessExtendArgs {
972
+ readonly consumer: AccessConsumer;
973
+ readonly consumerPrincipalId: PrincipalId;
974
+ readonly serviceId: string;
975
+ readonly added: readonly AccessExtendMember[];
976
+ readonly duration: AccessDuration | undefined;
977
+ }
978
+ type AccessExtendDecision = {
979
+ readonly granted: true;
980
+ readonly serviceId: string;
981
+ readonly grantedMembers: readonly {
982
+ interfaceId: string;
983
+ member: Pattern;
984
+ }[];
985
+ readonly capabilities?: readonly SignedCapability[];
986
+ } | {
987
+ readonly granted: false;
988
+ readonly reason?: string;
989
+ };
990
+ /**
991
+ * Optional per-permission invocation preview the consumer attaches to a
992
+ * direct ({@link HubAccessHandlers.onAccessRequestDirect}) request. It is
993
+ * **not** a signed authority constraint — it is the data the consent host
994
+ * needs to render an honest "Allow once" prompt and to pre-compute the
995
+ * `callBind.payloadHash` that binds a one-shot capability to exactly this
996
+ * call. `nonce`/`signedAtMs`/`interfaceHash` are the values the consumer
997
+ * will sign with when it actually issues the call.
998
+ */
999
+ interface AccessCallIntent {
1000
+ /** Fully-qualified method name the consumer intends to call. */
1001
+ readonly method: string;
1002
+ /** Params the consumer intends to send (shown to the user, hashed into callBind). */
1003
+ readonly params?: unknown;
1004
+ /** Schema-version assertion the call will carry. */
1005
+ readonly interfaceHash?: string;
1006
+ /** base64url nonce bytes the consumer will sign with. */
1007
+ readonly nonce: string;
1008
+ /** Unix milliseconds the consumer will sign with. */
1009
+ readonly signedAtMs: number;
1010
+ /** One-line summary for the prompt. */
1011
+ readonly summary?: string;
1012
+ /** Consumer's suggested default consent action. */
1013
+ readonly suggestion?: AccessDuration;
1014
+ }
1015
+ /**
1016
+ * An {@link AccessDirectArgs} permission, carrying the verbatim signed
1017
+ * {@link Permission} the consumer asked for plus the optional
1018
+ * {@link AccessCallIntent} that enables "Allow once" byte-binding.
1019
+ */
1020
+ interface AccessDirectPermission extends Permission {
1021
+ readonly callIntent?: AccessCallIntent;
1022
+ }
1023
+ interface AccessDirectArgs {
1024
+ readonly consumer: AccessConsumer;
1025
+ readonly consumerPrincipalId: PrincipalId;
1026
+ readonly permissions: readonly AccessDirectPermission[];
1027
+ readonly duration: AccessDuration | undefined;
1028
+ }
1029
+ type AccessDirectDecision = {
1030
+ readonly granted: true;
1031
+ readonly capabilities: readonly SignedCapability[];
1032
+ } | {
1033
+ readonly granted: false;
1034
+ readonly reason?: string;
1035
+ };
1036
+ interface HubAccessHandlers {
1037
+ /** Service-discovery grant: pick a service per slot and mint scoped caps. */
1038
+ onAccessRequest(args: AccessRequestArgs): Promise<AccessDecision>;
1039
+ /** Service-pinned widening of an existing grant. */
1040
+ onAccessExtend(args: AccessExtendArgs): Promise<AccessExtendDecision>;
1041
+ /** Verbatim attenuation grant (no discovery). */
1042
+ onAccessRequestDirect(args: AccessDirectArgs): Promise<AccessDirectDecision>;
1043
+ }
1044
+ interface RegisterHubAccessOptions {
1045
+ /** Consent + capability-issuance callbacks. */
1046
+ readonly handlers: HubAccessHandlers;
1047
+ /** Provides the directory snapshot for candidate resolution. */
1048
+ fetchDirectory(): Promise<readonly DirectoryEntry[]>;
1049
+ }
1050
+ /**
1051
+ * Install `hubAccess::{request,extend,requestAccess}` at the **connection
1052
+ * root** of a participant's overlay (root form, no serviceId prefix). The root
1053
+ * is never forwarded, so the consent front door is reached directly and needs
1054
+ * no capability of its own.
1055
+ *
1056
+ * The capability `audience` is taken **directly from `params.consumer.principal`**
1057
+ * — no longer derived from a verified call signer. A cap minted for a nodeId is
1058
+ * only usable by the holder of that node's key (enforced at call time by
1059
+ * `permits`, which checks `audience === call.signer`), so a self-asserted
1060
+ * audience grants no usable authority to a caller who does not hold the key.
1061
+ */
1062
+ declare function registerHubAccessService(connection: LinkRpcConnection<unknown>, options: RegisterHubAccessOptions): void;
1063
+ //#endregion
1064
+ //#region src/hub/server/tokenIdentityStore.d.ts
1065
+ /**
1066
+ * What a connection token resolves to once redeemed. The token is a hub-minted,
1067
+ * single-use bearer credential; its presence on a connection attests that the
1068
+ * minting authority (a `connectionTokenBinder`-granted endpoint) vouched for the
1069
+ * bound slot(s).
1070
+ *
1071
+ * The two bindings are **independent axes**, each optional:
1072
+ *
1073
+ * - {@link identitySlot} drives the redeemer's *managed identity* (`identity::*`).
1074
+ * - {@link grantedServiceIdNamespace} drives the redeemer's *freely-claimable
1075
+ * serviceId namespace* (`hubGrantedServiceId::get`).
1076
+ *
1077
+ * A binder typically sets both to the same value (the historical convention),
1078
+ * but they no longer have to agree — and either may be omitted. A token with
1079
+ * neither is still admitted, but the connection routes anonymously (no
1080
+ * `identity::*`, no freely-claimable namespace).
1081
+ */
1082
+ interface TokenIdentityBinding {
1083
+ /**
1084
+ * Managed-identity slot the redeeming connection inherits. Omit to admit the
1085
+ * connection without a managed identity.
1086
+ */
1087
+ readonly identitySlot?: string;
1088
+ /**
1089
+ * ServiceId namespace the redeeming connection may claim freely (reported
1090
+ * via `hubGrantedServiceId::get`). Omit to grant no freely-claimable
1091
+ * namespace.
1092
+ */
1093
+ readonly grantedServiceIdNamespace?: string;
1094
+ }
1095
+ /** A freshly minted token plus its absolute expiry (epoch ms). */
1096
+ interface MintedToken {
1097
+ readonly token: string;
1098
+ readonly expiresAt: number;
1099
+ }
1100
+ interface TokenIdentityStoreOptions {
1101
+ /** Default lifetime applied to {@link TokenIdentityStore.mint}. Default 30s. */
1102
+ readonly defaultTtlMs?: number;
1103
+ /** Clock seam (tests). Default `Date.now`. */
1104
+ readonly now?: () => number;
1105
+ /** Token generator seam (tests). Default 32 random bytes, base64url. */
1106
+ readonly generateToken?: () => string;
1107
+ }
1108
+ /**
1109
+ * The hub's connection-token mint + redemption store, shared between the
1110
+ * `connectionTokenBinder::bindConnectionToken` front door (mint) and a
1111
+ * `managedIdentity: { mode: "fromToken" }` / `grantedServiceId: { mode:
1112
+ * "fromToken" }` listener (redeem).
1113
+ *
1114
+ * Tokens are random, time-boxed and **single-use**: {@link redeem} consumes the
1115
+ * entry, so a token can bind exactly one connection and cannot be replayed once
1116
+ * used or expired. Expired entries are pruned lazily on access.
1117
+ */
1118
+ declare class TokenIdentityStore {
1119
+ private readonly _byToken;
1120
+ private readonly _defaultTtlMs;
1121
+ private readonly _now;
1122
+ private readonly _generate;
1123
+ constructor(options?: TokenIdentityStoreOptions);
1124
+ /** Mint a fresh single-use token bound to `binding`, valid for `ttlMs`. */
1125
+ mint(binding?: TokenIdentityBinding, ttlMs?: number): MintedToken;
1126
+ /**
1127
+ * Non-consuming existence check for the `hubrpc::initialize` token gate.
1128
+ * Returns `true` iff the token is live (known and unexpired). Single-use is
1129
+ * enforced separately by {@link redeem}.
1130
+ */
1131
+ peek(token: string | undefined): boolean;
1132
+ /**
1133
+ * Consume `token`, returning its binding exactly once. Returns `undefined`
1134
+ * for an unknown, expired or already-redeemed token (the caller should drop
1135
+ * the connection in that case).
1136
+ */
1137
+ redeem(token: string | undefined): TokenIdentityBinding | undefined;
1138
+ /** Live (unexpired, unredeemed) token count — primarily for tests/metrics. */
1139
+ get size(): number;
1140
+ private _prune;
1141
+ }
1142
+ //#endregion
1143
+ //#region src/hub/server/connectionTokenBinderService.d.ts
1144
+ interface RegisterConnectionTokenBinderOptions {
1145
+ /** Shared mint/redeem store (the same instance the redeem-side listener reads). */
1146
+ readonly store: TokenIdentityStore;
1147
+ /**
1148
+ * Literal `identitySlot` prefixes this binder may mint identities for (e.g.
1149
+ * `["docker/"]`). A `bindConnectionToken({ identitySlot })` call is rejected
1150
+ * unless `identitySlot` starts with one of these. Empty → identity binding
1151
+ * is disabled (any `identitySlot` request is rejected).
1152
+ */
1153
+ readonly identitySlotPrefixes: readonly string[];
1154
+ /**
1155
+ * Literal `serviceIdNamespace` prefixes this binder may grant (e.g.
1156
+ * `["docker/"]`). A `bindConnectionToken({ serviceIdNamespace })` call is
1157
+ * rejected unless `serviceIdNamespace` starts with one of these. Empty →
1158
+ * serviceId granting is disabled (any `serviceIdNamespace` request is
1159
+ * rejected).
1160
+ */
1161
+ readonly serviceIdPrefixes: readonly string[];
1162
+ /** Optional per-token TTL (ms); falls back to the store's default. */
1163
+ readonly ttlMs?: number;
1164
+ /**
1165
+ * Mount point when registering as a **forwarded hub service** (reachable via
1166
+ * routing as `<serviceId>::connectionTokenBinder`). Omit for the **root
1167
+ * form**: an interface-form registration on an overlay root, never forwarded
1168
+ * → never gated.
1169
+ */
1170
+ readonly serviceId?: string;
1171
+ }
1172
+ /**
1173
+ * Install `connectionTokenBinder::bindConnectionToken` on `connection`.
1174
+ *
1175
+ * Two forms, selected by {@link RegisterConnectionTokenBinderOptions.serviceId}:
1176
+ *
1177
+ * - **root form** (no `serviceId`): installed at a participant's overlay root
1178
+ * (root form, never forwarded → never gated). Safe because only the specific
1179
+ * trusted connection the acceptor installed it on can reach it.
1180
+ * - **forwarded form** (`serviceId` set): mounted as a routable hub service so
1181
+ * any participant can discover and call it. Safe only because forwarded calls
1182
+ * go through the hub's forwarded-call gate; the same prefix checks still bound
1183
+ * what any caller may mint.
1184
+ *
1185
+ * Each call mints a single-use token binding the requested `identitySlot` and/or
1186
+ * `serviceIdNamespace`, provided each requested field falls under the matching
1187
+ * configured prefix. Omitting a field leaves that axis unbound.
1188
+ */
1189
+ declare function registerConnectionTokenBinderService(connection: LinkRpcConnection<unknown>, options: RegisterConnectionTokenBinderOptions): void;
1190
+ //#endregion
1191
+ //#region src/hub/server/connectionHandler.d.ts
1192
+ /**
1193
+ * Everything a {@link ConnectionHandler} needs to install a participant's root
1194
+ * services. Built by {@link import('./hubConnectionAcceptor').HubConnectionAcceptor}
1195
+ * once per accepted connection: the connection-specific bits (`root`,
1196
+ * `upstream`, `transport`, `token`) plus the connection-independent policy the
1197
+ * acceptor was configured with (`hubServiceId`, `authorizeClaim`,
1198
+ * `installHubAccess`).
1199
+ */
1200
+ interface ConnectionContext {
1201
+ /** The overlay root to install this participant's front doors on. */
1202
+ readonly root: LinkRpcConnection<unknown>;
1203
+ /** The participant's link to the central hub (for `hubGrantedServiceId::register`). */
1204
+ readonly upstream: AttachedLink;
1205
+ /** The central hub. */
1206
+ readonly hub: Hub;
1207
+ /** The accepted transport (custom handlers may read attestation off it). */
1208
+ readonly transport: IMessageTransport;
1209
+ /** The `hubrpc::initialize` token the connection presented (if any). */
1210
+ readonly token: string | undefined;
1211
+ /** ServiceId the global reflection services are mounted under. */
1212
+ readonly hubServiceId: string;
1213
+ /** Per-connection claim authorizer (from the acceptor's policy), if any. */
1214
+ readonly authorizeClaim?: (requestedPrefix: ServiceId) => {
1215
+ ok: true;
1216
+ } | {
1217
+ ok: false;
1218
+ reason: string;
1219
+ };
1220
+ /** Installs the consent front door at the overlay root, if configured. */
1221
+ readonly installHubAccess?: (root: LinkRpcConnection<unknown>) => void;
1222
+ }
1223
+ /**
1224
+ * The resolved root services a claiming handler provisions. Every field is
1225
+ * optional; {@link provisionRoot} installs the always-on parts (claim/directory
1226
+ * front door + consent) unconditionally and the rest only when present.
1227
+ */
1228
+ interface RootProvision {
1229
+ /** Freely-claimable serviceId namespace (`hubGrantedServiceId::get`). */
1230
+ readonly grantedServiceIdNamespace?: string;
1231
+ /** Lazily-resolved managed identity (`identity::*`). Omit for no identity. */
1232
+ readonly resolveIdentity?: () => Promise<Identity>;
1233
+ /** Optional per-identity persistent storage (`identity.storage::*`). */
1234
+ readonly storage?: ManagedIdentityStorageBackend;
1235
+ /** Token-minting front door (`connectionTokenBinder::*`). Omit for none. */
1236
+ readonly connectionTokenBinder?: RegisterConnectionTokenBinderOptions;
1237
+ }
1238
+ /**
1239
+ * A claiming connection handler: installs the participant's root services and
1240
+ * returns. Built-ins delegate the actual registration to {@link provisionRoot}.
1241
+ */
1242
+ type ConnectionHandler = (ctx: ConnectionContext) => void;
1243
+ /**
1244
+ * Selects and (on claim) provisions a connection by its `hubrpc::initialize`
1245
+ * token. `handle` is **non-consuming** — it may be called at the pre-handshake
1246
+ * gate to answer "would you accept this token?" — and returns a
1247
+ * {@link ConnectionHandler} that does the (possibly consuming) installation, or
1248
+ * `undefined` to pass to the next factory.
1249
+ */
1250
+ interface ConnectionHandlerFactory {
1251
+ handle(token: string | undefined): ConnectionHandler | undefined;
1252
+ }
1253
+ /**
1254
+ * Walk `factories` in order; return the first handler that claims `token`, or
1255
+ * `undefined` if none do (the caller drops the connection).
1256
+ */
1257
+ declare function resolveConnectionHandler(factories: readonly ConnectionHandlerFactory[], token: string | undefined): ConnectionHandler | undefined;
1258
+ /**
1259
+ * Install a participant's root services from a resolved {@link RootProvision}:
1260
+ * the always-on claim/directory front door and consent surface, plus (when
1261
+ * present) the minting front door and lazy managed identity. This is the single
1262
+ * shared installer every built-in handler funnels through.
1263
+ */
1264
+ declare function provisionRoot(ctx: ConnectionContext, provision: RootProvision): void;
1265
+ /**
1266
+ * A factory that claims **any** connection (optionally gated by `claims`) and
1267
+ * provisions a fixed root. Used for the `anonymous` handler (claims all), the
1268
+ * `static` handler (claims a matching token), and dial-in endpoints (claims all,
1269
+ * no token).
1270
+ */
1271
+ declare function fixedProvisionHandler(provision: RootProvision, claims?: (token: string | undefined) => boolean): ConnectionHandlerFactory;
1272
+ /** The `anonymous` handler: claims every connection, provisioning `provision`. */
1273
+ declare function anonymousHandler(provision: RootProvision): ConnectionHandlerFactory;
1274
+ /** The `static` handler: claims connections whose token equals `value`. */
1275
+ declare function staticTokenHandler(value: string, provision: RootProvision): ConnectionHandlerFactory;
1276
+ /**
1277
+ * The `bound` handler: claims connections whose token is live in `store`, and on
1278
+ * claim **redeems** it (single-use) to derive the provisioned identity slot
1279
+ * and/or granted serviceId namespace. `resolveSlot` turns a redeemed slot into
1280
+ * a lazy identity resolver plus its per-identity storage (injected so this stays
1281
+ * node-agnostic).
1282
+ */
1283
+ declare function boundTokenHandler(store: TokenIdentityStore, resolveSlot: (slot: string) => {
1284
+ resolveIdentity: () => Promise<Identity>;
1285
+ storage: ManagedIdentityStorageBackend;
1286
+ }): ConnectionHandlerFactory;
1287
+ //#endregion
1288
+ //#region src/hub/server/provenance.d.ts
1289
+ /**
1290
+ * Attested origin of an incoming connection. Produced by a
1291
+ * {@link ConnectionProvenanceProvider} from a freshly accepted transport,
1292
+ * *before* any RPC flows. Everything here is, by contract, already verified:
1293
+ * there is no "unverified" provenance — a provider that cannot fully attest
1294
+ * the peer returns `{ error }` instead of a partial result.
1295
+ */
1296
+ interface ConnectionProvenance {
1297
+ /**
1298
+ * Stable identity key for the peer. INVARIANT: must begin with
1299
+ * `` `${provider.identityNamespace}/` `` (e.g. `"docker/echo-provider"`),
1300
+ * which {@link withProvenance} enforces — a value that doesn't is treated
1301
+ * as un-attested. This keeps keys from different provenance sources from
1302
+ * colliding and makes the namespace self-describing.
1303
+ */
1304
+ readonly identityKey: string;
1305
+ /**
1306
+ * Free-form, already-verified attestation detail for logging and policy
1307
+ * (container id, image, pid, uid, …). Never used for routing or trust.
1308
+ */
1309
+ readonly attributes: Readonly<Record<string, string | number>>;
1310
+ }
1311
+ /**
1312
+ * Resolves the {@link ConnectionProvenance} of an accepted transport.
1313
+ *
1314
+ * Generic over the transport type so the provider can read whatever the
1315
+ * concrete transport exposes (e.g. a `NodeSocketTransport`'s `socket` for
1316
+ * peercred) — the hub core stays free of those types. Implementations live
1317
+ * outside the hub (e.g. `@hediet/linkrpc-extra`).
1318
+ */
1319
+ interface ConnectionProvenanceProvider<TTransport extends Transport> {
1320
+ /**
1321
+ * Namespace every {@link ConnectionProvenance.identityKey} this provider
1322
+ * emits is prefixed with (e.g. `"docker"`).
1323
+ */
1324
+ readonly identityNamespace: string;
1325
+ /**
1326
+ * Resolve provenance for `transport` before any RPC is exchanged. Honour
1327
+ * `signal` to abort if the transport closes or a deadline elapses.
1328
+ *
1329
+ * @returns verified provenance, or `{ error }` to refuse attestation.
1330
+ */
1331
+ resolve(transport: TTransport, signal: AbortSignal): Promise<ConnectionProvenance | {
1332
+ error: string;
1333
+ }>;
1334
+ }
1335
+ /**
1336
+ * A {@link Transport} annotated with its (possibly absent) attestation —
1337
+ * the narrow shape provenance-aware consumers (e.g. {@link PrefixPolicy})
1338
+ * depend on, without naming a concrete backend transport type.
1339
+ */
1340
+ interface ITransportWithProvenance extends Transport {
1341
+ readonly provenance: ConnectionProvenance | undefined;
1342
+ }
1343
+ /** A concrete transport `T` annotated with its (possibly absent) attestation. */
1344
+ type WithProvenance<T extends Transport> = T & {
1345
+ readonly provenance: ConnectionProvenance | undefined;
1346
+ };
1347
+ interface WithProvenanceOptions {
1348
+ /**
1349
+ * Abort the provider's `resolve` after this many ms (counts as
1350
+ * un-attested). Omit for no deadline.
1351
+ */
1352
+ readonly resolveTimeoutMs?: number;
1353
+ /**
1354
+ * Drop connections that could not be attested instead of forwarding them
1355
+ * with `provenance: undefined`. Default `false`.
1356
+ */
1357
+ readonly requireProvenance?: boolean;
1358
+ }
1359
+ /**
1360
+ * Annotate every transport from `source` with the provenance `provider`
1361
+ * resolves for it. On provider error/timeout — or when the returned
1362
+ * `identityKey` does not start with `` `${provider.identityNamespace}/` `` —
1363
+ * the transport is forwarded with `provenance: undefined`, unless
1364
+ * {@link WithProvenanceOptions.requireProvenance} is set, in which case it is
1365
+ * disposed and dropped.
1366
+ *
1367
+ * This is the one concrete {@link mapTransport} the hub ships: it is where
1368
+ * `net.Socket` (or any backend handle) is read for attestation and nowhere
1369
+ * else.
1370
+ */
1371
+ declare function withProvenance<T extends Transport>(source: ITransportServer<T>, provider: ConnectionProvenanceProvider<T>, options?: WithProvenanceOptions): ITransportServer<WithProvenance<T>>;
1372
+ //#endregion
1373
+ //#region src/hub/server/prefixPolicy.d.ts
1374
+ /** What a prefix-claim is judged against. */
1375
+ interface ClaimContext<TTransport extends Transport> {
1376
+ /**
1377
+ * Node id of the participant's hub-minted managed identity, or `undefined`
1378
+ * if the connection has no identity (anonymous routing).
1379
+ */
1380
+ readonly principal: PrincipalId | undefined;
1381
+ /** The connecting transport (may carry provenance, depending on `T`). */
1382
+ readonly transport: TTransport;
1383
+ /** ServiceId prefix the participant is trying to claim. */
1384
+ readonly requestedPrefix: ServiceId;
1385
+ }
1386
+ /**
1387
+ * Decides whether a participant may own a routing prefix. This is the policy
1388
+ * wrapped around the hub's single privileged write seam (`claimPrefix`) — the
1389
+ * only place identity/provenance gates routing.
1390
+ */
1391
+ interface PrefixPolicy<TTransport extends Transport> {
1392
+ authorizeClaim(ctx: ClaimContext<TTransport>): {
1393
+ ok: true;
1394
+ } | {
1395
+ ok: false;
1396
+ reason: string;
1397
+ };
1398
+ }
1399
+ interface PrincipalIdPrefixPolicyOptions<TTransport extends ITransportWithProvenance> {
1400
+ /**
1401
+ * Prefixes a participant is allowed to claim. Default: the transport's
1402
+ * attested {@link ConnectionProvenance.identityKey} used **verbatim** as
1403
+ * the sole claimable prefix (e.g. `"docker/echo-provider"`). The full,
1404
+ * namespace-scoped key is the prefix — it is never split or rewritten. A
1405
+ * connection with no provenance may claim nothing.
1406
+ */
1407
+ derivePrefixes?(ctx: ClaimContext<TTransport>): readonly ServiceId[];
1408
+ }
1409
+ /**
1410
+ * Default {@link PrefixPolicy}: a participant may claim only the prefix(es)
1411
+ * {@link PrincipalIdPrefixPolicyOptions.derivePrefixes} grants it. The out-of-box
1412
+ * derivation ties the claimable prefix to the peer's attested provenance, so
1413
+ * `docker/echo-provider` can serve `docker/echo-provider/*` and nothing can
1414
+ * impersonate it.
1415
+ */
1416
+ declare class PrincipalIdPrefixPolicy<TTransport extends ITransportWithProvenance> implements PrefixPolicy<TTransport> {
1417
+ private readonly _derive;
1418
+ constructor(options?: PrincipalIdPrefixPolicyOptions<TTransport>);
1419
+ authorizeClaim(ctx: ClaimContext<TTransport>): {
1420
+ ok: true;
1421
+ } | {
1422
+ ok: false;
1423
+ reason: string;
1424
+ };
1425
+ }
1426
+ //#endregion
1427
+ //#region src/hub/server/hubConnectionAcceptor.d.ts
1428
+ /**
1429
+ * A data shape describing an imperative consent front door — the handlers plus
1430
+ * the directory source `registerHubAccessService` needs. Retained as a
1431
+ * convenience type; the acceptor installs consent via the more general
1432
+ * {@link HubConnectionAcceptorBaseOptions.installHubAccess} callback.
1433
+ */
1434
+ interface HubAccessConfig {
1435
+ readonly handlers: HubAccessHandlers;
1436
+ fetchDirectory(): Promise<readonly DirectoryEntry[]>;
1437
+ }
1438
+ interface HubConnectionAcceptorBaseOptions<TTransport extends Transport> {
1439
+ /**
1440
+ * Source of inbound transports. Often a provenance-annotated server
1441
+ * (`withProvenance(socketServer, provider)`), but any
1442
+ * {@link ITransportServer} works — provenance is read via `resolveIdentity`
1443
+ * and the policy, not required by the acceptor's types.
1444
+ */
1445
+ readonly server: ITransportServer<TTransport>;
1446
+ /** The central hub all accepted participants attach under. */
1447
+ readonly hub: Hub;
1448
+ /**
1449
+ * Ordered connection handlers. For each accepted transport the acceptor
1450
+ * resolves the first handler whose {@link ConnectionHandlerFactory.handle}
1451
+ * claims the presented `hubrpc::initialize` token; that handler installs the
1452
+ * participant's root services (identity, granted namespace, minting, …). If
1453
+ * no handler claims, the connection is dropped. An empty array accepts
1454
+ * nothing.
1455
+ */
1456
+ readonly handlers: readonly ConnectionHandlerFactory[];
1457
+ /**
1458
+ * Authorizes each prefix claim a participant makes. Omit to allow every
1459
+ * well-formed claim.
1460
+ */
1461
+ readonly policy?: PrefixPolicy<TTransport>;
1462
+ /** ServiceId of the global reflection services. Defaults to `'hub'`. */
1463
+ readonly hubServiceId?: string;
1464
+ /**
1465
+ * Install the consent front door at each accepted participant's overlay
1466
+ * root (root form, never forwarded → never gated). The callback receives the
1467
+ * root connection and registers `hubAccess::*` on it however it likes — the
1468
+ * imperative `registerHubAccessService(root, …)` or the keyless
1469
+ * `HubAccessManifestHost.registerHubAccessAtRoot(root)`. Omit on open hubs;
1470
+ * then no consent surface is served.
1471
+ */
1472
+ installHubAccess?(root: LinkRpcConnection<unknown>): void;
1473
+ /** Fired after a participant's overlay is attached. */
1474
+ onAttached?(info: {
1475
+ overlay: RootOverlay;
1476
+ }): void;
1477
+ /** Fired when accepting a connection throws. */
1478
+ onError?(error: Error): void;
1479
+ }
1480
+ /**
1481
+ * Forwarded-call gating policy — a **discriminated union** so the type system
1482
+ * enforces the safe combinations and rules out the dangerous "ask for
1483
+ * capability enforcement but forget the trust anchors" misconfiguration:
1484
+ *
1485
+ * - **off** (default): forwarded calls reach the hub unverified.
1486
+ * - **authenticity only** (`verifyForwardedCalls: true`): forwarded calls must
1487
+ * carry a valid `$hubrpc` signature; `adminIds` is optional.
1488
+ * - **capability** (`verifyForwardedCalls: true` + `requireForwardedCapability:
1489
+ * true`): forwarded calls must additionally present a capability rooted at one
1490
+ * of `adminIds`. Because an empty/absent anchor set fails closed (rejecting
1491
+ * *every* capability, including validly granted ones), `adminIds` is
1492
+ * **required at compile time** in this arm.
1493
+ */
1494
+ type ForwardCheckingPolicy = {
1495
+ readonly verifyForwardedCalls?: false;
1496
+ readonly requireForwardedCapability?: false;
1497
+ readonly adminIds?: undefined;
1498
+ } | {
1499
+ /**
1500
+ * Every accepted participant's hub-facing link is wrapped in a
1501
+ * {@link withForwardedCallGate signature front door}: forwarded
1502
+ * (fully-qualified) requests must carry a valid `$hubrpc` signature to
1503
+ * reach the hub. Requests to the hub's own `hubServiceId` prefix are
1504
+ * exempt (those services self-gate).
1505
+ */
1506
+ readonly verifyForwardedCalls: true;
1507
+ readonly requireForwardedCapability?: false;
1508
+ /**
1509
+ * Optional root trust anchors. With authenticity-only verification a
1510
+ * capability is not required, but when present a call's chain is still
1511
+ * checked against these anchors.
1512
+ */
1513
+ readonly adminIds?: Iterable<PrincipalId>;
1514
+ } | {
1515
+ readonly verifyForwardedCalls: true;
1516
+ /**
1517
+ * The forwarded-call gate also requires a capability, not just a
1518
+ * signature: a forwarded call authorises only if its capability chain
1519
+ * roots at one of {@link adminIds} (accepted for every service). The
1520
+ * gate also dedups each request nonce, so `callBind` grants are
1521
+ * single-use for free.
1522
+ */
1523
+ readonly requireForwardedCapability: true;
1524
+ /** Root trust anchors. Typically the hub admin's PrincipalId. Required (fail-closed otherwise). */
1525
+ readonly adminIds: Iterable<PrincipalId>;
1526
+ };
1527
+ type HubConnectionAcceptorOptions<TTransport extends Transport> = HubConnectionAcceptorBaseOptions<TTransport> & ForwardCheckingPolicy;
1528
+ /**
1529
+ * Bridges accepted transports onto the hubv2 graph via a pluggable chain of
1530
+ * {@link ConnectionHandlerFactory connection handlers}. For each transport it:
1531
+ *
1532
+ * 1. attaches an uplink to the central hub and builds a {@link RootOverlay};
1533
+ * 2. resolves the first handler whose token check claims the connection and
1534
+ * lets it install the participant's root services (claim/directory front
1535
+ * door + consent, and optionally identity, granted namespace, minting) —
1536
+ * dropping the connection if none claim; and
1537
+ * 3. connects the participant, disposing the overlay and detaching the uplink
1538
+ * (which releases its claimed prefixes) when the transport closes.
1539
+ *
1540
+ * It never names a backend transport type — hand it any {@link ITransportServer}.
1541
+ */
1542
+ declare class HubConnectionAcceptor<TTransport extends Transport> {
1543
+ private readonly _options;
1544
+ private readonly _accepted;
1545
+ private _disposed;
1546
+ constructor(_options: HubConnectionAcceptorOptions<TTransport>);
1547
+ private _accept;
1548
+ /**
1549
+ * Wrap the hub-facing link per the {@link ForwardCheckingPolicy}. The
1550
+ * option union guarantees that capability mode always carries its
1551
+ * `adminIds` trust anchors, so this can never silently fail closed by
1552
+ * forgetting them.
1553
+ */
1554
+ private _gateHubFacing;
1555
+ private _wire;
1556
+ dispose(): void;
1557
+ }
1558
+ //#endregion
1559
+ export { AccessSlotRequest as $, AccessCallIntent as A, PendingRequestInfo as At, AccessRequestArgs as B, staticTokenHandler as C, HubTrafficSubscription as Ct, TokenIdentityBinding as D, HubOptions as Dt, MintedToken as E, Hub as Et, AccessDirectPermission as F, AccessDurationName as G, HubAccessHandlers as H, AccessDuration as I, ProposeArgs as J, CapabilityProposal as K, AccessExtendArgs as L, AccessDecision as M, AccessDirectArgs as N, TokenIdentityStore as O, IDisposable as Ot, AccessDirectDecision as P, AccessSlotCandidate as Q, AccessExtendDecision as R, resolveConnectionHandler as S, HubTrafficSource as St, registerConnectionTokenBinderService as T, AttachedLink as Tt, RegisterHubAccessOptions as U, AccessSlotBinding as V, registerHubAccessService as W, AccessInterfaceRequirement as X, durationToExp as Y, AccessMemberRequirement as Z, RootProvision as _, RootOverlay as _t, PrefixPolicy as a, resolveAccessCandidates as at, fixedProvisionHandler as b, OverlaySplitterInspection as bt, ConnectionProvenance as c, createHubServiceInterfaces as ct, WithProvenance as d, registerHubServiceIdRegistry as dt, DirectoryEntry as et, WithProvenanceOptions as f, withRequestIdContext as ft, ConnectionHandlerFactory as g, registerIdentityServices as gt, ConnectionHandler as h, registerHubServices as ht, ClaimContext as i, fetchFullDirectory as it, AccessConsumer as j, TokenIdentityStoreOptions as k, IHubLogger as kt, ConnectionProvenanceProvider as l, HubRegisterOptions as lt, ConnectionContext as m, RegisterIdentityServicesOptions as mt, HubConnectionAcceptor as n, ResolvedCandidates as nt, PrincipalIdPrefixPolicy as o, HubServices as ot, withProvenance as p, RegisterHubServicesOptions as pt, CapabilityProposalIssuer as q, HubConnectionAcceptorOptions as r, candidatesForSlot as rt, PrincipalIdPrefixPolicyOptions as s, HubServicesOptions as st, HubAccessConfig as t, ResolvedAccessSlot as tt, ITransportWithProvenance as u, RegisterCallContext as ut, anonymousHandler as v, RootOverlayOptions as vt, RegisterConnectionTokenBinderOptions as w, HubTrafficWatchOptions as wt, provisionRoot as x, HubInspector as xt, boundTokenHandler as y, OverlaySplitter as yt, AccessExtendMember as z };
1560
+ //# sourceMappingURL=hubConnectionAcceptor-BwydvFa4.d.ts.map