@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,526 @@
|
|
|
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 { C as defineInterface, N as requestType } from "./boundedTrafficSubscription-1L592xc7.js";
|
|
6
|
+
import { array, boolean, discriminatedUnion, enum as enum$1, extend, literal, minLength, number, object, optional, record, string, union, unknown } from "zod/mini";
|
|
7
|
+
//#region src/hub/common/hub.interfaces.ts
|
|
8
|
+
const zMemberPattern = union([object({ exact: string() }), object({ prefix: string() })]);
|
|
9
|
+
/**
|
|
10
|
+
* Optional per-permission invocation preview the requester suggests for
|
|
11
|
+
* "Allow once" consent UX. When present, the user can bind the resulting
|
|
12
|
+
* capability to this exact (method, params, interfaceHash) tuple.
|
|
13
|
+
*
|
|
14
|
+
* `nonce` and `signedAtMs` are consumer-chosen and identify the exact call
|
|
15
|
+
* attempt the cap will authorise. The host hashes the same canonical
|
|
16
|
+
* bytes the consumer will sign; the gate re-derives the hash from the
|
|
17
|
+
* inbound call and compares. The consumer MUST use the same
|
|
18
|
+
* nonce+signedAtMs when it actually issues the call (`@hediet/linkrpc`'s
|
|
19
|
+
* {@link JsonRpcChannel.setCapProvider} hook does this automatically).
|
|
20
|
+
*
|
|
21
|
+
* Everything in `params` is shown to the user verbatim.
|
|
22
|
+
*/
|
|
23
|
+
const zCallIntent = object({
|
|
24
|
+
/** Fully-qualified method name the requester intends to call. */
|
|
25
|
+
method: string(),
|
|
26
|
+
/** Exact params the requester intends to send. */
|
|
27
|
+
params: optional(unknown()),
|
|
28
|
+
/** Optional schema hash assertion the call carries inside the signed payload. */
|
|
29
|
+
interfaceHash: optional(string()),
|
|
30
|
+
/** base64url-encoded nonce bytes the consumer will sign with. */
|
|
31
|
+
nonce: string(),
|
|
32
|
+
/** Unix milliseconds the consumer will sign with. */
|
|
33
|
+
signedAtMs: number(),
|
|
34
|
+
/** Optional one-line summary the shell displays next to params. */
|
|
35
|
+
summary: optional(string()),
|
|
36
|
+
/** Requester's preferred default action (`once` if omitted). */
|
|
37
|
+
suggestion: optional(enum$1([
|
|
38
|
+
"once",
|
|
39
|
+
"shortLived",
|
|
40
|
+
"longLived",
|
|
41
|
+
"persistent"
|
|
42
|
+
]))
|
|
43
|
+
});
|
|
44
|
+
const zParamMatcher = union([
|
|
45
|
+
object({ exact: unknown() }),
|
|
46
|
+
object({ enum: array(unknown()) }),
|
|
47
|
+
object({ prefix: string() }),
|
|
48
|
+
object({ subsetOf: array(string()) }),
|
|
49
|
+
object({ any: literal(true) })
|
|
50
|
+
]);
|
|
51
|
+
const zCallBindHash = object({
|
|
52
|
+
alg: literal("sha256"),
|
|
53
|
+
payloadHash: string()
|
|
54
|
+
});
|
|
55
|
+
/** Wire shape of `TargetPattern` from `@hediet/linkrpc`. */
|
|
56
|
+
const zTargetPattern = object({
|
|
57
|
+
serviceId: zMemberPattern,
|
|
58
|
+
interfaceId: zMemberPattern,
|
|
59
|
+
interfaceHash: optional(string()),
|
|
60
|
+
members: array(zMemberPattern)
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* Wire shape of `Permission` from `@hediet/linkrpc/identity/capability`.
|
|
64
|
+
* Used by `hubAccess::requestAccess` so the consumer can describe exactly
|
|
65
|
+
* what authority it wants — including wildcard service ids
|
|
66
|
+
* (`target.serviceId: { prefix: "" }`). `canInvoke`/`canDelegate` default
|
|
67
|
+
* to `false` (fail closed).
|
|
68
|
+
*/
|
|
69
|
+
const zPermission = object({
|
|
70
|
+
target: zTargetPattern,
|
|
71
|
+
canInvoke: optional(boolean()),
|
|
72
|
+
canDelegate: optional(boolean()),
|
|
73
|
+
params: optional(record(string(), zParamMatcher)),
|
|
74
|
+
callBind: optional(zCallBindHash)
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* A `requestAccess` permission carrying the consent-only {@link zCallIntent}
|
|
78
|
+
* preview alongside the authority it requests. The host strips `callIntent`
|
|
79
|
+
* before minting the signed capability.
|
|
80
|
+
*/
|
|
81
|
+
const zRequestPermission = extend(zPermission, { callIntent: optional(zCallIntent) });
|
|
82
|
+
/**
|
|
83
|
+
* Who is asking, plus the `principal` that becomes the minted capability's
|
|
84
|
+
* `audience`/target. The hub uses `principal` directly (it is no longer derived
|
|
85
|
+
* from the call's verified signer): a cap minted for a `principal` is only usable
|
|
86
|
+
* by the holder of that principal's key, enforced at call time
|
|
87
|
+
* (`permits` checks `audience === call.signer`).
|
|
88
|
+
*/
|
|
89
|
+
const zConsumer = object({
|
|
90
|
+
name: string(),
|
|
91
|
+
/** Capability audience/target — the consumer's own PrincipalId. */
|
|
92
|
+
principal: string(),
|
|
93
|
+
origin: optional(string()),
|
|
94
|
+
purpose: optional(string())
|
|
95
|
+
});
|
|
96
|
+
/**
|
|
97
|
+
* Wire shape of `SignedCapability` from `@hediet/linkrpc`. Mirrors the
|
|
98
|
+
* structural type without pulling zod into the identity package. Keep in
|
|
99
|
+
* sync with `Capability` / `SignedCapability` there.
|
|
100
|
+
*
|
|
101
|
+
* A capability is now a flat object (its fields) plus a `$hubrpcSignature`
|
|
102
|
+
* map carrying the issuer's `capability` signature. Delegation links to a
|
|
103
|
+
* single `parentHash` (the parent's `signedHash("capability", ...)`).
|
|
104
|
+
*/
|
|
105
|
+
const zSignedCapability = object({
|
|
106
|
+
issuer: string(),
|
|
107
|
+
audience: string(),
|
|
108
|
+
permissions: array(zPermission),
|
|
109
|
+
expiresAtMs: optional(number()),
|
|
110
|
+
parentHash: optional(string()),
|
|
111
|
+
nonce: string(),
|
|
112
|
+
$hubrpcSignature: object({
|
|
113
|
+
capability: optional(object({
|
|
114
|
+
keyId: string(),
|
|
115
|
+
sig: string()
|
|
116
|
+
})),
|
|
117
|
+
call: optional(object({
|
|
118
|
+
keyId: string(),
|
|
119
|
+
sig: string()
|
|
120
|
+
}))
|
|
121
|
+
})
|
|
122
|
+
});
|
|
123
|
+
/** Lifetime hint for a minted capability. Shared across hubAccess + the manifest. */
|
|
124
|
+
const zDuration = enum$1([
|
|
125
|
+
"once",
|
|
126
|
+
"shortLived",
|
|
127
|
+
"longLived",
|
|
128
|
+
"persistent"
|
|
129
|
+
]);
|
|
130
|
+
/** One interface a slot's chosen service must implement. */
|
|
131
|
+
const zInterfaceRef = object({
|
|
132
|
+
id: string(),
|
|
133
|
+
hash: optional(string()),
|
|
134
|
+
/** Default true. */
|
|
135
|
+
required: optional(boolean())
|
|
136
|
+
});
|
|
137
|
+
/** One member (method) the consumer intends to call on a slot's interface. */
|
|
138
|
+
const zMemberRequest = object({
|
|
139
|
+
interfaceId: string(),
|
|
140
|
+
member: zMemberPattern,
|
|
141
|
+
/** Default true. */
|
|
142
|
+
required: optional(boolean())
|
|
143
|
+
});
|
|
144
|
+
/**
|
|
145
|
+
* A consumer's need for ONE service: the interfaces it must speak and the
|
|
146
|
+
* members it intends to call. The discovery side of both `hubAccess::request`
|
|
147
|
+
* (sent as a call param) and `hubAccessManifest` (published as state).
|
|
148
|
+
*/
|
|
149
|
+
const zSlotRequest = object({
|
|
150
|
+
interfaces: array(zInterfaceRef),
|
|
151
|
+
members: optional(array(zMemberRequest))
|
|
152
|
+
});
|
|
153
|
+
/** The resolution of one slot: exactly one chosen service. */
|
|
154
|
+
const zResolvedSlot = object({
|
|
155
|
+
serviceId: string(),
|
|
156
|
+
satisfiedInterfaces: array(string())
|
|
157
|
+
});
|
|
158
|
+
/**
|
|
159
|
+
* Minimal JSON-document patch op, addressed by an RFC 6901 JSON Pointer.
|
|
160
|
+
* `path: ""` targets the whole document; a pointer like `/granted/secretStore`
|
|
161
|
+
* targets one entry. Used by `hubAccessManifest::setCurrent` to replace the
|
|
162
|
+
* entire granted document or a single entry.
|
|
163
|
+
*/
|
|
164
|
+
const zManifestPatch = discriminatedUnion("op", [object({
|
|
165
|
+
op: literal("set"),
|
|
166
|
+
path: string(),
|
|
167
|
+
value: unknown()
|
|
168
|
+
}), object({
|
|
169
|
+
op: literal("remove"),
|
|
170
|
+
path: string()
|
|
171
|
+
})]);
|
|
172
|
+
/**
|
|
173
|
+
* `hubServiceIdRegistry::registerServiceId` — a participant claims a prefix
|
|
174
|
+
* **outside** its provenance-granted namespace.
|
|
175
|
+
*
|
|
176
|
+
* Reached under the hub's own service id (form-3
|
|
177
|
+
* `<hubServiceId>::hubServiceIdRegistry::registerServiceId`) and gated by an
|
|
178
|
+
* admin-rooted capability. For claims **within** the connection's granted
|
|
179
|
+
* namespace, use the cheaper, capability-free `hubGrantedServiceId::register`
|
|
180
|
+
* instead.
|
|
181
|
+
*/
|
|
182
|
+
const hubServiceIdRegistryInterface = defineInterface({
|
|
183
|
+
id: "hubServiceIdRegistry",
|
|
184
|
+
description: "Claim a serviceId prefix on the hub."
|
|
185
|
+
}, { registerServiceId: requestType(object({ requestedPrefix: string().check(minLength(1)) }), object({})) });
|
|
186
|
+
/**
|
|
187
|
+
* `hubGrantedServiceId` — the ungated connection surface, served at the
|
|
188
|
+
* connection root on each participant's overlay (so it is reachable directly,
|
|
189
|
+
* never forwarded, and needs no capability of its own). Two jobs:
|
|
190
|
+
*
|
|
191
|
+
* - `get` (unsigned) reports the topology facts the hub decided for this
|
|
192
|
+
* connection — where it is and what it may claim.
|
|
193
|
+
* - `register` (provenance-gated) claims a prefix **within**
|
|
194
|
+
* this connection's granted namespace — the capability-free claim path.
|
|
195
|
+
* Claims outside the namespace go through the admin-gated
|
|
196
|
+
* `<hubServiceId>::hubServiceIdRegistry::registerServiceId` door instead.
|
|
197
|
+
* - `getHubServiceId` (unsigned) reports the serviceId prefix the hub mounts
|
|
198
|
+
* its own global services under — the prefix to address the admin-gated
|
|
199
|
+
* registry and reflection endpoints through. Served at the connection root
|
|
200
|
+
* so a participant can discover it **before** it knows where the hub lives.
|
|
201
|
+
*
|
|
202
|
+
* The hub's consent front door (`hubAccess::*`) is served at the connection
|
|
203
|
+
* root too, so it needs no bootstrap capability and is reached directly.
|
|
204
|
+
*/
|
|
205
|
+
const hubGrantedServiceIdInterface = defineInterface({
|
|
206
|
+
id: "hubGrantedServiceId",
|
|
207
|
+
description: "Connection facts and capability-free serviceId claims."
|
|
208
|
+
}, {
|
|
209
|
+
/**
|
|
210
|
+
* Connection facts (unsigned). The participant pulls, on each
|
|
211
|
+
* (re)connect, the topology facts the hub has decided for this
|
|
212
|
+
* connection:
|
|
213
|
+
*
|
|
214
|
+
* - `grantedServiceIdNamespace` — the absolute serviceId region this
|
|
215
|
+
* connection's provenance may claim (anything at/under it). May be
|
|
216
|
+
* the empty string, meaning "claim nothing freely".
|
|
217
|
+
*/
|
|
218
|
+
get: requestType(object({}), object({ grantedServiceIdNamespace: string() })),
|
|
219
|
+
/**
|
|
220
|
+
* The serviceId prefix the hub mounts its own global services under
|
|
221
|
+
* (default `'hub'`). Use it to address the admin-gated
|
|
222
|
+
* `<hubServiceId>::hubServiceIdRegistry::registerServiceId` door and the
|
|
223
|
+
* hub's reflection endpoints. Served at the connection root (unsigned),
|
|
224
|
+
* so a participant can learn it without first knowing where the hub
|
|
225
|
+
* lives.
|
|
226
|
+
*/
|
|
227
|
+
getHubServiceId: requestType(object({}), object({ hubServiceId: string() })),
|
|
228
|
+
/**
|
|
229
|
+
* Claim a serviceId prefix **within** this connection's provenance-
|
|
230
|
+
* granted namespace (see `get().grantedServiceIdNamespace`). No
|
|
231
|
+
* capability needed — the grant already happened out-of-band at attach
|
|
232
|
+
* time. The requested `serviceId` must equal the granted namespace or be
|
|
233
|
+
* nested beneath it; anything else is rejected (claim it through the
|
|
234
|
+
* admin-gated `<hubServiceId>::hubServiceIdRegistry::registerServiceId`
|
|
235
|
+
* door instead).
|
|
236
|
+
*/
|
|
237
|
+
register: requestType(object({ serviceId: string().check(minLength(1)) }), object({}))
|
|
238
|
+
});
|
|
239
|
+
/**
|
|
240
|
+
* `hubAccess::request` — a consumer (e.g. a sandboxed web editor) asks the
|
|
241
|
+
* hub for scoped access to one or more services.
|
|
242
|
+
*
|
|
243
|
+
* Consumers describe their needs as named *dependencies* ("slots"): each
|
|
244
|
+
* slot lists the interfaces the chosen service must implement and the
|
|
245
|
+
* methods the consumer wants to call. The hub resolves candidate services
|
|
246
|
+
* from its participant directory, then forwards the bundle to a host-
|
|
247
|
+
* supplied handler (see `Hub` options) that shows the user a single
|
|
248
|
+
* prompt. The handler picks a concrete service per slot and approves a
|
|
249
|
+
* subset of the requested methods.
|
|
250
|
+
*
|
|
251
|
+
* In v1 the response carries only the resolution (which serviceId was
|
|
252
|
+
* chosen per slot). Once the hub holds a signing identity it will also
|
|
253
|
+
* return a `SignedCapability` the consumer attaches to subsequent calls.
|
|
254
|
+
* Until then, dispatch is not gated on the grant — see plan-access.md.
|
|
255
|
+
*/
|
|
256
|
+
const hubAccessInterface = defineInterface({
|
|
257
|
+
id: "hubAccess",
|
|
258
|
+
description: "Consumer requests scoped access to services on the hub."
|
|
259
|
+
}, {
|
|
260
|
+
request: requestType(object({
|
|
261
|
+
consumer: zConsumer,
|
|
262
|
+
dependencies: record(string(), zSlotRequest),
|
|
263
|
+
duration: optional(zDuration)
|
|
264
|
+
}), discriminatedUnion("status", [
|
|
265
|
+
object({
|
|
266
|
+
status: literal("granted"),
|
|
267
|
+
slots: record(string(), zResolvedSlot),
|
|
268
|
+
/**
|
|
269
|
+
* One or more `SignedCapability`s issued by the hub
|
|
270
|
+
* (audience = consumer NodeId). Consumers attach them
|
|
271
|
+
* via `$hubrpc.capabilities` on subsequent calls. Empty
|
|
272
|
+
* array is legal (hub with no signing identity / tests).
|
|
273
|
+
*
|
|
274
|
+
* Plural so a handler can return per-slot caps with
|
|
275
|
+
* different `exp`/caveats; typical handlers return a
|
|
276
|
+
* single cap covering every granted member.
|
|
277
|
+
*/
|
|
278
|
+
capabilities: array(zSignedCapability)
|
|
279
|
+
}),
|
|
280
|
+
object({
|
|
281
|
+
status: literal("denied"),
|
|
282
|
+
reason: optional(string())
|
|
283
|
+
}),
|
|
284
|
+
object({
|
|
285
|
+
status: literal("noCandidates"),
|
|
286
|
+
/** Slot ids that have zero matching candidates. */
|
|
287
|
+
slots: array(string())
|
|
288
|
+
})
|
|
289
|
+
])),
|
|
290
|
+
/**
|
|
291
|
+
* Service-pinned widening of an existing grant. The consumer asks
|
|
292
|
+
* the hub for additional members on a `serviceId` they already
|
|
293
|
+
* deal with — same audience (their NodeId) and (intended) same
|
|
294
|
+
* `rootIssuer` as the prior grant. The hub never picks the service
|
|
295
|
+
* for the consumer here: `serviceId` is an input, not a result.
|
|
296
|
+
*
|
|
297
|
+
* On grant the response carries a fresh `SignedCapability` whose
|
|
298
|
+
* attenuations cover **only** the granted delta. Bag-compatible
|
|
299
|
+
* with the prior cap; combine via `merge` (when it exists) or
|
|
300
|
+
* just keep both in `$hubrpc.capabilities`.
|
|
301
|
+
*
|
|
302
|
+
* Distinguished from `request` so the consent UI can render a
|
|
303
|
+
* different affordance ("X already has read access on `github`,
|
|
304
|
+
* grant `update` as well?" instead of from-zero selection).
|
|
305
|
+
*
|
|
306
|
+
* TODO(hub-ledger): enforce "consumer has prior history on this
|
|
307
|
+
* service" — see `hub.ts:_handleAccessExtend`. v1 forwards
|
|
308
|
+
* directly to the host's `onAccessExtend` without consulting the
|
|
309
|
+
* `_grants` ledger; this is by design while we settle on the
|
|
310
|
+
* persistence model.
|
|
311
|
+
*/
|
|
312
|
+
extend: requestType(object({
|
|
313
|
+
consumer: zConsumer,
|
|
314
|
+
/**
|
|
315
|
+
* Service to widen the grant on. MUST equal the
|
|
316
|
+
* `serviceId` of a previously-issued attenuation for this
|
|
317
|
+
* consumer NodeId. v1 does not validate this.
|
|
318
|
+
*/
|
|
319
|
+
serviceId: string(),
|
|
320
|
+
/**
|
|
321
|
+
* The delta. Same shape as `request`'s slot.members. Each
|
|
322
|
+
* entry MUST refer to an interface the consumer has been
|
|
323
|
+
* introduced to on `serviceId` via a prior `request`.
|
|
324
|
+
*/
|
|
325
|
+
added: array(zMemberRequest),
|
|
326
|
+
duration: optional(zDuration)
|
|
327
|
+
}), discriminatedUnion("status", [object({
|
|
328
|
+
status: literal("granted"),
|
|
329
|
+
serviceId: string(),
|
|
330
|
+
/** Members actually granted (handler may approve a subset). */
|
|
331
|
+
granted: array(object({
|
|
332
|
+
interfaceId: string(),
|
|
333
|
+
member: zMemberPattern
|
|
334
|
+
})),
|
|
335
|
+
/**
|
|
336
|
+
* Capability covering only the granted delta. Audience
|
|
337
|
+
* = consumer NodeId. Bag-compatible with the prior
|
|
338
|
+
* cap on this service.
|
|
339
|
+
*/
|
|
340
|
+
capabilities: optional(array(zSignedCapability))
|
|
341
|
+
}), object({
|
|
342
|
+
status: literal("denied"),
|
|
343
|
+
reason: optional(string())
|
|
344
|
+
})])),
|
|
345
|
+
/**
|
|
346
|
+
* `hubAccess::requestAccess` — direct capability request. The
|
|
347
|
+
* consumer specifies the exact attenuations it wants. No
|
|
348
|
+
* service-discovery, no candidate resolution: the consumer
|
|
349
|
+
* already knows which `(serviceId, interfaceId, members)` it
|
|
350
|
+
* needs, including wildcards (e.g. `serviceId: { prefix: "" }`
|
|
351
|
+
* to ask for an interface anywhere).
|
|
352
|
+
*
|
|
353
|
+
* Compared to `request`:
|
|
354
|
+
* - `request` does directory-based discovery, picks one service
|
|
355
|
+
* per slot, and returns a cap pinned to that service. Use
|
|
356
|
+
* when the consumer says "give me SOME service that does X".
|
|
357
|
+
* - `requestAccess` is verbatim. Use when the consumer says
|
|
358
|
+
* "give me exactly these attenuations". Especially useful for
|
|
359
|
+
* reflection (`hubrpc.directory::list` on any service) and
|
|
360
|
+
* for on-demand per-method grants from an explorer-style UI.
|
|
361
|
+
*
|
|
362
|
+
* The user prompt shows the exact `Capability` the hub will sign
|
|
363
|
+
* on Allow, same byte-equality guarantee as `request`/`extend`.
|
|
364
|
+
*/
|
|
365
|
+
requestAccess: requestType(object({
|
|
366
|
+
consumer: zConsumer,
|
|
367
|
+
permissions: array(zRequestPermission).check(minLength(1)),
|
|
368
|
+
duration: optional(zDuration)
|
|
369
|
+
}), discriminatedUnion("status", [object({
|
|
370
|
+
status: literal("granted"),
|
|
371
|
+
capabilities: array(zSignedCapability)
|
|
372
|
+
}), object({
|
|
373
|
+
status: literal("denied"),
|
|
374
|
+
reason: optional(string())
|
|
375
|
+
})]))
|
|
376
|
+
});
|
|
377
|
+
/**
|
|
378
|
+
* The root issuer(s) whose signature the participant will accept on the minted
|
|
379
|
+
* capability. A capability is only usable by its holder if it chains to a root
|
|
380
|
+
* that the *target* service trusts — and not every service trusts every root.
|
|
381
|
+
* So the participant declares, per entry, which minting root(s) would produce a
|
|
382
|
+
* cap it can actually use; the admin must mint with one of them (or decline).
|
|
383
|
+
*
|
|
384
|
+
* Tri-state, by design:
|
|
385
|
+
* - **omitted** (`undefined`) — unspecified. The participant doesn't know /
|
|
386
|
+
* doesn't constrain the root; the admin picks. (The common, lenient default.)
|
|
387
|
+
* - **non-empty** (`["id:abc", …]`) — mint with one of *these* roots; a cap
|
|
388
|
+
* rooted elsewhere is useless to the participant.
|
|
389
|
+
* - **empty** (`[]`) — "no root is acceptable", i.e. nothing can satisfy this.
|
|
390
|
+
* Almost certainly a bug (it can never be granted); kept representable so a
|
|
391
|
+
* tool can detect and flag it rather than silently treating it as "any".
|
|
392
|
+
*/
|
|
393
|
+
const zAcceptableRootIds = array(string());
|
|
394
|
+
/**
|
|
395
|
+
* A desired access entry the participant publishes. Two kinds, mirroring the
|
|
396
|
+
* two discovery-vs-verbatim halves of `hubAccess`:
|
|
397
|
+
*
|
|
398
|
+
* - `discover` — "find me a service implementing X" (same shape as a
|
|
399
|
+
* `hubAccess::request` slot). The admin resolves one concrete service.
|
|
400
|
+
* - `direct` — "grant exactly these permissions" (same shape as
|
|
401
|
+
* `hubAccess::requestAccess`). The serviceId is already in each permission's
|
|
402
|
+
* target; no discovery.
|
|
403
|
+
*/
|
|
404
|
+
const zManifestRequest = discriminatedUnion("kind", [extend(zSlotRequest, {
|
|
405
|
+
kind: literal("discover"),
|
|
406
|
+
/** Who is asking; `principal` is the audience minted caps must target. */
|
|
407
|
+
consumer: zConsumer,
|
|
408
|
+
/** Human rationale shown to the admin ("seal tokens at rest"). */
|
|
409
|
+
reason: optional(string()),
|
|
410
|
+
duration: optional(zDuration),
|
|
411
|
+
/** Root issuer(s) that must mint this cap. See {@link zAcceptableRootIds}. */
|
|
412
|
+
acceptableRootIds: optional(zAcceptableRootIds),
|
|
413
|
+
/**
|
|
414
|
+
* Host-authored provenance bag, stamped at `registerHubAccessAtRoot`
|
|
415
|
+
* (NOT taken from the consumer's request params — so it cannot be
|
|
416
|
+
* spoofed). An open `record<string, unknown>` so a host can attach
|
|
417
|
+
* whatever origin info it observed about the requesting transport (e.g.
|
|
418
|
+
* `{ sourceTransportId }` to route a local consent UI). Meaningful only
|
|
419
|
+
* to a consumer that KNOWS this manifest is local; an aggregating
|
|
420
|
+
* manifest MUST re-scope or omit it.
|
|
421
|
+
*/
|
|
422
|
+
origin: optional(record(string(), unknown()))
|
|
423
|
+
}), object({
|
|
424
|
+
kind: literal("direct"),
|
|
425
|
+
/** Who is asking; `principal` is the audience minted caps must target. */
|
|
426
|
+
consumer: zConsumer,
|
|
427
|
+
reason: optional(string()),
|
|
428
|
+
permissions: array(zRequestPermission).check(minLength(1)),
|
|
429
|
+
duration: optional(zDuration),
|
|
430
|
+
/** Root issuer(s) that must mint this cap. See {@link zAcceptableRootIds}. */
|
|
431
|
+
acceptableRootIds: optional(zAcceptableRootIds),
|
|
432
|
+
/** Host-authored provenance bag. See the `discover` variant. */
|
|
433
|
+
origin: optional(record(string(), unknown()))
|
|
434
|
+
})]);
|
|
435
|
+
/**
|
|
436
|
+
* The current (granted) value of one entry. For `discover` it carries the single
|
|
437
|
+
* chosen service (mirroring `hubAccess::request`'s resolved slot) plus the caps;
|
|
438
|
+
* for `direct` just the caps (the serviceId is in each permission's target).
|
|
439
|
+
*/
|
|
440
|
+
const zGrantedEntry = discriminatedUnion("kind", [extend(zResolvedSlot, {
|
|
441
|
+
kind: literal("discover"),
|
|
442
|
+
capabilities: array(zSignedCapability)
|
|
443
|
+
}), object({
|
|
444
|
+
kind: literal("direct"),
|
|
445
|
+
capabilities: array(zSignedCapability)
|
|
446
|
+
})]);
|
|
447
|
+
/**
|
|
448
|
+
* The current value of one entry: `granted` (caps, plus the chosen service for
|
|
449
|
+
* `discover`) or `denied` (with an optional reason). An entry *absent* from the
|
|
450
|
+
* current document is **undecided** — distinct from a terminal `denied`, which
|
|
451
|
+
* lets an approver say "no" (and why) instead of silently withholding.
|
|
452
|
+
*/
|
|
453
|
+
const zCurrentEntry = discriminatedUnion("status", [object({
|
|
454
|
+
status: literal("granted"),
|
|
455
|
+
granted: zGrantedEntry
|
|
456
|
+
}), object({
|
|
457
|
+
status: literal("denied"),
|
|
458
|
+
reason: optional(string())
|
|
459
|
+
})]);
|
|
460
|
+
/**
|
|
461
|
+
* `hubAccessManifest` — the declarative twin of {@link hubAccessInterface}.
|
|
462
|
+
*
|
|
463
|
+
* Where `hubAccess` is the imperative, just-in-time door a consumer *calls* (and
|
|
464
|
+
* the hub serves at the connection root), `hubAccessManifest` is **served by the
|
|
465
|
+
* participant** under its own serviceId, so it appears in `hubrpc.directory::list`
|
|
466
|
+
* — its very presence is the request. A participant publishes its DESIRED access
|
|
467
|
+
* entries (keyed by id, like `request`'s `dependencies`); an admin discovers
|
|
468
|
+
* them via the directory, mints capabilities **with its own identity** (a
|
|
469
|
+
* configured hub capability root), and writes the CURRENT/granted state back via
|
|
470
|
+
* patches. The participant reads the granted entries and attaches the caps to
|
|
471
|
+
* its later calls.
|
|
472
|
+
*
|
|
473
|
+
* Reconcile model (desired vs. current), aligned with the hub's other surfaces:
|
|
474
|
+
* - `getDesired` / `watchDesired` — the participant's declared needs.
|
|
475
|
+
* - `getCurrent` / `setCurrent` / `watchCurrent` — the admin-written grants.
|
|
476
|
+
*
|
|
477
|
+
* `watch*` follows the coarse empty-tick convention of `hubrpc.directory::watch`:
|
|
478
|
+
* a tick means "re-`get` now", keeping the server stateless (no per-item deltas).
|
|
479
|
+
*/
|
|
480
|
+
const hubAccessManifestInterface = defineInterface({
|
|
481
|
+
id: "hubAccessManifest",
|
|
482
|
+
description: "Participant-served declarative access: publish DESIRED access entries (discover | direct), an admin mints capabilities and writes the CURRENT granted state back. The reconcile twin of hubAccess::{request,requestAccess}."
|
|
483
|
+
}, {
|
|
484
|
+
/** The full desired document: who is asking and the entries it wants. */
|
|
485
|
+
getDesired: requestType(object({}), object({
|
|
486
|
+
/**
|
|
487
|
+
* entryId → desired entry, each carrying its own `consumer`. A
|
|
488
|
+
* per-participant manifest's entries all share one consumer; a hub
|
|
489
|
+
* broker aggregates entries from many consumers in one flat map.
|
|
490
|
+
*/
|
|
491
|
+
requested: record(string(), zManifestRequest),
|
|
492
|
+
/** Bumped whenever `requested` changes; lets watchers dedupe ticks. */
|
|
493
|
+
revision: number()
|
|
494
|
+
})).withStream({ client: object({}) }),
|
|
495
|
+
/**
|
|
496
|
+
* Coarse change tap on the desired document. Emits an empty tick when
|
|
497
|
+
* `requested` may have changed; the caller re-`getDesired`. Resolves when
|
|
498
|
+
* the caller cancels. Mirrors `hubrpc.directory::watch`.
|
|
499
|
+
*/
|
|
500
|
+
watchDesired: requestType(object({}), object({})).withStream({ server: object({}) }),
|
|
501
|
+
/** The full current document: entryId → granted/denied (absent ⇒ undecided). */
|
|
502
|
+
getCurrent: requestType(object({}), object({
|
|
503
|
+
/** entryId → current value (granted | denied). */
|
|
504
|
+
current: record(string(), zCurrentEntry),
|
|
505
|
+
/** Bumped on every successful `setCurrent`. */
|
|
506
|
+
revision: number()
|
|
507
|
+
})),
|
|
508
|
+
/**
|
|
509
|
+
* Apply patches to the current document. Replace the whole document with
|
|
510
|
+
* `{ op: 'set', path: '', value }`, or a single entry with
|
|
511
|
+
* `{ op: 'set', path: '/current/<entryId>', value }` (a `zCurrentEntry`:
|
|
512
|
+
* granted or denied). Admin-only in practice (gated by a capability
|
|
513
|
+
* rooted at an accepted issuer).
|
|
514
|
+
*/
|
|
515
|
+
setCurrent: requestType(object({ patches: array(zManifestPatch) }), object({ revision: number() })).withStream({ client: object({}) }),
|
|
516
|
+
/**
|
|
517
|
+
* Coarse change tap on the current document. Emits an empty tick when
|
|
518
|
+
* `current` may have changed; the participant re-`getCurrent` and applies
|
|
519
|
+
* any new capabilities. Resolves when the caller cancels.
|
|
520
|
+
*/
|
|
521
|
+
watchCurrent: requestType(object({}), object({})).withStream({ server: object({}) })
|
|
522
|
+
});
|
|
523
|
+
//#endregion
|
|
524
|
+
export { hubServiceIdRegistryInterface as i, hubAccessManifestInterface as n, hubGrantedServiceIdInterface as r, hubAccessInterface as t };
|
|
525
|
+
|
|
526
|
+
//# sourceMappingURL=hub.interfaces-BzWfsVT2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub.interfaces-BzWfsVT2.js","names":["zEnum"],"sources":["../../src/hub/common/hub.interfaces.ts"],"sourcesContent":["import { array, boolean, discriminatedUnion, enum as zEnum, extend, literal, minLength, number, object, optional, record, string, union, unknown } from 'zod/mini';\nimport { defineInterface, requestType, type InterfaceClient } from '../../index';\n\nconst zMemberPattern = union([\n object({ exact: string() }),\n object({ prefix: string() }),\n]);\n\n/**\n * Optional per-permission invocation preview the requester suggests for\n * \"Allow once\" consent UX. When present, the user can bind the resulting\n * capability to this exact (method, params, interfaceHash) tuple.\n *\n * `nonce` and `signedAtMs` are consumer-chosen and identify the exact call\n * attempt the cap will authorise. The host hashes the same canonical\n * bytes the consumer will sign; the gate re-derives the hash from the\n * inbound call and compares. The consumer MUST use the same\n * nonce+signedAtMs when it actually issues the call (`@hediet/linkrpc`'s\n * {@link JsonRpcChannel.setCapProvider} hook does this automatically).\n *\n * Everything in `params` is shown to the user verbatim.\n */\nconst zCallIntent = object({\n /** Fully-qualified method name the requester intends to call. */\n method: string(),\n /** Exact params the requester intends to send. */\n params: optional(unknown()),\n /** Optional schema hash assertion the call carries inside the signed payload. */\n interfaceHash: optional(string()),\n /** base64url-encoded nonce bytes the consumer will sign with. */\n nonce: string(),\n /** Unix milliseconds the consumer will sign with. */\n signedAtMs: number(),\n /** Optional one-line summary the shell displays next to params. */\n summary: optional(string()),\n /** Requester's preferred default action (`once` if omitted). */\n suggestion: optional(zEnum(['once', 'shortLived', 'longLived', 'persistent'])),\n});\n\nconst zParamMatcher = union([\n object({ exact: unknown() }),\n object({ enum: array(unknown()) }),\n object({ prefix: string() }),\n object({ subsetOf: array(string()) }),\n object({ any: literal(true) }),\n]);\n\nconst zCallBindHash = object({\n alg: literal('sha256'),\n payloadHash: string(),\n});\n\n/** Wire shape of `TargetPattern` from `@hediet/linkrpc`. */\nconst zTargetPattern = object({\n serviceId: zMemberPattern,\n interfaceId: zMemberPattern,\n interfaceHash: optional(string()),\n members: array(zMemberPattern),\n});\n\n/**\n * Wire shape of `Permission` from `@hediet/linkrpc/identity/capability`.\n * Used by `hubAccess::requestAccess` so the consumer can describe exactly\n * what authority it wants — including wildcard service ids\n * (`target.serviceId: { prefix: \"\" }`). `canInvoke`/`canDelegate` default\n * to `false` (fail closed).\n */\nconst zPermission = object({\n target: zTargetPattern,\n canInvoke: optional(boolean()),\n canDelegate: optional(boolean()),\n params: optional(record(string(), zParamMatcher)),\n callBind: optional(zCallBindHash),\n});\n\n/**\n * A `requestAccess` permission carrying the consent-only {@link zCallIntent}\n * preview alongside the authority it requests. The host strips `callIntent`\n * before minting the signed capability.\n */\nconst zRequestPermission = extend(zPermission, {\n callIntent: optional(zCallIntent),\n});\n\n/**\n * Who is asking, plus the `principal` that becomes the minted capability's\n * `audience`/target. The hub uses `principal` directly (it is no longer derived\n * from the call's verified signer): a cap minted for a `principal` is only usable\n * by the holder of that principal's key, enforced at call time\n * (`permits` checks `audience === call.signer`).\n */\nconst zConsumer = object({\n name: string(),\n /** Capability audience/target — the consumer's own PrincipalId. */\n principal: string(),\n origin: optional(string()),\n purpose: optional(string()),\n});\n\n/**\n * Wire shape of `SignedCapability` from `@hediet/linkrpc`. Mirrors the\n * structural type without pulling zod into the identity package. Keep in\n * sync with `Capability` / `SignedCapability` there.\n *\n * A capability is now a flat object (its fields) plus a `$hubrpcSignature`\n * map carrying the issuer's `capability` signature. Delegation links to a\n * single `parentHash` (the parent's `signedHash(\"capability\", ...)`).\n */\nconst zSignedCapability = object({\n issuer: string(),\n audience: string(),\n permissions: array(zPermission),\n expiresAtMs: optional(number()),\n parentHash: optional(string()),\n nonce: string(),\n $hubrpcSignature: object({\n capability: optional(object({ keyId: string(), sig: string() })),\n call: optional(object({ keyId: string(), sig: string() })),\n }),\n});\n\n/** Lifetime hint for a minted capability. Shared across hubAccess + the manifest. */\nconst zDuration = zEnum(['once', 'shortLived', 'longLived', 'persistent']);\n\n// ---- discovery slot shapes (shared by hubAccess.request and hubAccessManifest) ----\n\n/** One interface a slot's chosen service must implement. */\nconst zInterfaceRef = object({\n id: string(),\n hash: optional(string()),\n /** Default true. */\n required: optional(boolean()),\n});\n\n/** One member (method) the consumer intends to call on a slot's interface. */\nconst zMemberRequest = object({\n interfaceId: string(),\n member: zMemberPattern,\n /** Default true. */\n required: optional(boolean()),\n});\n\n/**\n * A consumer's need for ONE service: the interfaces it must speak and the\n * members it intends to call. The discovery side of both `hubAccess::request`\n * (sent as a call param) and `hubAccessManifest` (published as state).\n */\nconst zSlotRequest = object({\n interfaces: array(zInterfaceRef),\n members: optional(array(zMemberRequest)),\n});\n\n/** The resolution of one slot: exactly one chosen service. */\nconst zResolvedSlot = object({\n serviceId: string(),\n satisfiedInterfaces: array(string()),\n});\n\n/**\n * Minimal JSON-document patch op, addressed by an RFC 6901 JSON Pointer.\n * `path: \"\"` targets the whole document; a pointer like `/granted/secretStore`\n * targets one entry. Used by `hubAccessManifest::setCurrent` to replace the\n * entire granted document or a single entry.\n */\nconst zManifestPatch = discriminatedUnion('op', [\n object({ op: literal('set'), path: string(), value: unknown() }),\n object({ op: literal('remove'), path: string() }),\n]);\n\n/**\n * `hubServiceIdRegistry::registerServiceId` — a participant claims a prefix\n * **outside** its provenance-granted namespace.\n *\n * Reached under the hub's own service id (form-3\n * `<hubServiceId>::hubServiceIdRegistry::registerServiceId`) and gated by an\n * admin-rooted capability. For claims **within** the connection's granted\n * namespace, use the cheaper, capability-free `hubGrantedServiceId::register`\n * instead.\n */\nexport const hubServiceIdRegistryInterface = defineInterface(\n {\n id: 'hubServiceIdRegistry',\n description: 'Claim a serviceId prefix on the hub.',\n },\n {\n registerServiceId: requestType(\n object({\n requestedPrefix: string().check(minLength(1)),\n }),\n object({}),\n ),\n },\n);\n\n\n/**\n * `hubGrantedServiceId` — the ungated connection surface, served at the\n * connection root on each participant's overlay (so it is reachable directly,\n * never forwarded, and needs no capability of its own). Two jobs:\n *\n * - `get` (unsigned) reports the topology facts the hub decided for this\n * connection — where it is and what it may claim.\n * - `register` (provenance-gated) claims a prefix **within**\n * this connection's granted namespace — the capability-free claim path.\n * Claims outside the namespace go through the admin-gated\n * `<hubServiceId>::hubServiceIdRegistry::registerServiceId` door instead.\n * - `getHubServiceId` (unsigned) reports the serviceId prefix the hub mounts\n * its own global services under — the prefix to address the admin-gated\n * registry and reflection endpoints through. Served at the connection root\n * so a participant can discover it **before** it knows where the hub lives.\n *\n * The hub's consent front door (`hubAccess::*`) is served at the connection\n * root too, so it needs no bootstrap capability and is reached directly.\n */\nexport const hubGrantedServiceIdInterface = defineInterface(\n {\n id: 'hubGrantedServiceId',\n description: 'Connection facts and capability-free serviceId claims.',\n },\n {\n /**\n * Connection facts (unsigned). The participant pulls, on each\n * (re)connect, the topology facts the hub has decided for this\n * connection:\n *\n * - `grantedServiceIdNamespace` — the absolute serviceId region this\n * connection's provenance may claim (anything at/under it). May be\n * the empty string, meaning \"claim nothing freely\".\n */\n get: requestType(\n object({}),\n object({\n grantedServiceIdNamespace: string(),\n }),\n ),\n\n /**\n * The serviceId prefix the hub mounts its own global services under\n * (default `'hub'`). Use it to address the admin-gated\n * `<hubServiceId>::hubServiceIdRegistry::registerServiceId` door and the\n * hub's reflection endpoints. Served at the connection root (unsigned),\n * so a participant can learn it without first knowing where the hub\n * lives.\n */\n getHubServiceId: requestType(\n object({}),\n object({\n hubServiceId: string(),\n }),\n ),\n\n /**\n * Claim a serviceId prefix **within** this connection's provenance-\n * granted namespace (see `get().grantedServiceIdNamespace`). No\n * capability needed — the grant already happened out-of-band at attach\n * time. The requested `serviceId` must equal the granted namespace or be\n * nested beneath it; anything else is rejected (claim it through the\n * admin-gated `<hubServiceId>::hubServiceIdRegistry::registerServiceId`\n * door instead).\n */\n register: requestType(\n object({ serviceId: string().check(minLength(1)) }),\n object({}),\n ),\n },\n);\n\n/**\n * `hubAccess::request` — a consumer (e.g. a sandboxed web editor) asks the\n * hub for scoped access to one or more services.\n *\n * Consumers describe their needs as named *dependencies* (\"slots\"): each\n * slot lists the interfaces the chosen service must implement and the\n * methods the consumer wants to call. The hub resolves candidate services\n * from its participant directory, then forwards the bundle to a host-\n * supplied handler (see `Hub` options) that shows the user a single\n * prompt. The handler picks a concrete service per slot and approves a\n * subset of the requested methods.\n *\n * In v1 the response carries only the resolution (which serviceId was\n * chosen per slot). Once the hub holds a signing identity it will also\n * return a `SignedCapability` the consumer attaches to subsequent calls.\n * Until then, dispatch is not gated on the grant — see plan-access.md.\n */\nexport const hubAccessInterface = defineInterface(\n {\n id: 'hubAccess',\n description: 'Consumer requests scoped access to services on the hub.',\n },\n {\n request: requestType(\n object({\n consumer: zConsumer,\n dependencies: record(string(), zSlotRequest),\n duration: optional(zDuration),\n }),\n discriminatedUnion('status', [\n object({\n status: literal('granted'),\n slots: record(string(), zResolvedSlot),\n /**\n * One or more `SignedCapability`s issued by the hub\n * (audience = consumer NodeId). Consumers attach them\n * via `$hubrpc.capabilities` on subsequent calls. Empty\n * array is legal (hub with no signing identity / tests).\n *\n * Plural so a handler can return per-slot caps with\n * different `exp`/caveats; typical handlers return a\n * single cap covering every granted member.\n */\n capabilities: array(zSignedCapability),\n }),\n object({\n status: literal('denied'),\n reason: optional(string()),\n }),\n object({\n status: literal('noCandidates'),\n /** Slot ids that have zero matching candidates. */\n slots: array(string()),\n }),\n ]),\n ),\n\n /**\n * Service-pinned widening of an existing grant. The consumer asks\n * the hub for additional members on a `serviceId` they already\n * deal with — same audience (their NodeId) and (intended) same\n * `rootIssuer` as the prior grant. The hub never picks the service\n * for the consumer here: `serviceId` is an input, not a result.\n *\n * On grant the response carries a fresh `SignedCapability` whose\n * attenuations cover **only** the granted delta. Bag-compatible\n * with the prior cap; combine via `merge` (when it exists) or\n * just keep both in `$hubrpc.capabilities`.\n *\n * Distinguished from `request` so the consent UI can render a\n * different affordance (\"X already has read access on `github`,\n * grant `update` as well?\" instead of from-zero selection).\n *\n * TODO(hub-ledger): enforce \"consumer has prior history on this\n * service\" — see `hub.ts:_handleAccessExtend`. v1 forwards\n * directly to the host's `onAccessExtend` without consulting the\n * `_grants` ledger; this is by design while we settle on the\n * persistence model.\n */\n extend: requestType(\n object({\n consumer: zConsumer,\n /**\n * Service to widen the grant on. MUST equal the\n * `serviceId` of a previously-issued attenuation for this\n * consumer NodeId. v1 does not validate this.\n */\n serviceId: string(),\n /**\n * The delta. Same shape as `request`'s slot.members. Each\n * entry MUST refer to an interface the consumer has been\n * introduced to on `serviceId` via a prior `request`.\n */\n added: array(zMemberRequest),\n duration: optional(zDuration),\n }),\n discriminatedUnion('status', [\n object({\n status: literal('granted'),\n serviceId: string(),\n /** Members actually granted (handler may approve a subset). */\n granted: array(\n object({\n interfaceId: string(),\n member: zMemberPattern,\n }),\n ),\n /**\n * Capability covering only the granted delta. Audience\n * = consumer NodeId. Bag-compatible with the prior\n * cap on this service.\n */\n capabilities: optional(array(zSignedCapability)),\n }),\n object({\n status: literal('denied'),\n reason: optional(string()),\n }),\n // TODO(hub-ledger): additional variants once the host's\n // grant ledger is persisted:\n // - { status: \"unknownService\", serviceId }\n // - { status: \"unknownInterface\", serviceId, interfaceIds }\n // Reject without ever prompting the user when the consumer\n // is asking to widen a service it has no prior grant on.\n ]),\n ),\n\n /**\n * `hubAccess::requestAccess` — direct capability request. The\n * consumer specifies the exact attenuations it wants. No\n * service-discovery, no candidate resolution: the consumer\n * already knows which `(serviceId, interfaceId, members)` it\n * needs, including wildcards (e.g. `serviceId: { prefix: \"\" }`\n * to ask for an interface anywhere).\n *\n * Compared to `request`:\n * - `request` does directory-based discovery, picks one service\n * per slot, and returns a cap pinned to that service. Use\n * when the consumer says \"give me SOME service that does X\".\n * - `requestAccess` is verbatim. Use when the consumer says\n * \"give me exactly these attenuations\". Especially useful for\n * reflection (`hubrpc.directory::list` on any service) and\n * for on-demand per-method grants from an explorer-style UI.\n *\n * The user prompt shows the exact `Capability` the hub will sign\n * on Allow, same byte-equality guarantee as `request`/`extend`.\n */\n requestAccess: requestType(\n object({\n consumer: zConsumer,\n permissions: array(zRequestPermission).check(minLength(1)),\n duration: optional(zDuration),\n }),\n discriminatedUnion('status', [\n object({\n status: literal('granted'),\n capabilities: array(zSignedCapability),\n }),\n object({\n status: literal('denied'),\n reason: optional(string()),\n }),\n ]),\n ),\n },\n);\n\n// ════════════════════════════════════════════════════════════════════════\n// hubAccessManifest — the declarative twin of `hubAccess`.\n// ════════════════════════════════════════════════════════════════════════\n\n/**\n * The root issuer(s) whose signature the participant will accept on the minted\n * capability. A capability is only usable by its holder if it chains to a root\n * that the *target* service trusts — and not every service trusts every root.\n * So the participant declares, per entry, which minting root(s) would produce a\n * cap it can actually use; the admin must mint with one of them (or decline).\n *\n * Tri-state, by design:\n * - **omitted** (`undefined`) — unspecified. The participant doesn't know /\n * doesn't constrain the root; the admin picks. (The common, lenient default.)\n * - **non-empty** (`[\"id:abc\", …]`) — mint with one of *these* roots; a cap\n * rooted elsewhere is useless to the participant.\n * - **empty** (`[]`) — \"no root is acceptable\", i.e. nothing can satisfy this.\n * Almost certainly a bug (it can never be granted); kept representable so a\n * tool can detect and flag it rather than silently treating it as \"any\".\n */\nconst zAcceptableRootIds = array(string());\n\n/**\n * A desired access entry the participant publishes. Two kinds, mirroring the\n * two discovery-vs-verbatim halves of `hubAccess`:\n *\n * - `discover` — \"find me a service implementing X\" (same shape as a\n * `hubAccess::request` slot). The admin resolves one concrete service.\n * - `direct` — \"grant exactly these permissions\" (same shape as\n * `hubAccess::requestAccess`). The serviceId is already in each permission's\n * target; no discovery.\n */\nconst zManifestRequest = discriminatedUnion('kind', [\n extend(zSlotRequest, {\n kind: literal('discover'),\n /** Who is asking; `principal` is the audience minted caps must target. */\n consumer: zConsumer,\n /** Human rationale shown to the admin (\"seal tokens at rest\"). */\n reason: optional(string()),\n duration: optional(zDuration),\n /** Root issuer(s) that must mint this cap. See {@link zAcceptableRootIds}. */\n acceptableRootIds: optional(zAcceptableRootIds),\n /**\n * Host-authored provenance bag, stamped at `registerHubAccessAtRoot`\n * (NOT taken from the consumer's request params — so it cannot be\n * spoofed). An open `record<string, unknown>` so a host can attach\n * whatever origin info it observed about the requesting transport (e.g.\n * `{ sourceTransportId }` to route a local consent UI). Meaningful only\n * to a consumer that KNOWS this manifest is local; an aggregating\n * manifest MUST re-scope or omit it.\n */\n origin: optional(record(string(), unknown())),\n }),\n object({\n kind: literal('direct'),\n /** Who is asking; `principal` is the audience minted caps must target. */\n consumer: zConsumer,\n reason: optional(string()),\n permissions: array(zRequestPermission).check(minLength(1)),\n duration: optional(zDuration),\n /** Root issuer(s) that must mint this cap. See {@link zAcceptableRootIds}. */\n acceptableRootIds: optional(zAcceptableRootIds),\n /** Host-authored provenance bag. See the `discover` variant. */\n origin: optional(record(string(), unknown())),\n }),\n]);\n\n/**\n * The current (granted) value of one entry. For `discover` it carries the single\n * chosen service (mirroring `hubAccess::request`'s resolved slot) plus the caps;\n * for `direct` just the caps (the serviceId is in each permission's target).\n */\nconst zGrantedEntry = discriminatedUnion('kind', [\n extend(zResolvedSlot, {\n kind: literal('discover'),\n capabilities: array(zSignedCapability),\n }),\n object({\n kind: literal('direct'),\n capabilities: array(zSignedCapability),\n }),\n]);\n\n/**\n * The current value of one entry: `granted` (caps, plus the chosen service for\n * `discover`) or `denied` (with an optional reason). An entry *absent* from the\n * current document is **undecided** — distinct from a terminal `denied`, which\n * lets an approver say \"no\" (and why) instead of silently withholding.\n */\nconst zCurrentEntry = discriminatedUnion('status', [\n object({ status: literal('granted'), granted: zGrantedEntry }),\n object({ status: literal('denied'), reason: optional(string()) }),\n]);\n\n/**\n * `hubAccessManifest` — the declarative twin of {@link hubAccessInterface}.\n *\n * Where `hubAccess` is the imperative, just-in-time door a consumer *calls* (and\n * the hub serves at the connection root), `hubAccessManifest` is **served by the\n * participant** under its own serviceId, so it appears in `hubrpc.directory::list`\n * — its very presence is the request. A participant publishes its DESIRED access\n * entries (keyed by id, like `request`'s `dependencies`); an admin discovers\n * them via the directory, mints capabilities **with its own identity** (a\n * configured hub capability root), and writes the CURRENT/granted state back via\n * patches. The participant reads the granted entries and attaches the caps to\n * its later calls.\n *\n * Reconcile model (desired vs. current), aligned with the hub's other surfaces:\n * - `getDesired` / `watchDesired` — the participant's declared needs.\n * - `getCurrent` / `setCurrent` / `watchCurrent` — the admin-written grants.\n *\n * `watch*` follows the coarse empty-tick convention of `hubrpc.directory::watch`:\n * a tick means \"re-`get` now\", keeping the server stateless (no per-item deltas).\n */\nexport const hubAccessManifestInterface = defineInterface(\n {\n id: 'hubAccessManifest',\n description:\n 'Participant-served declarative access: publish DESIRED access entries '\n + '(discover | direct), an admin mints capabilities and writes the CURRENT '\n + 'granted state back. The reconcile twin of hubAccess::{request,requestAccess}.',\n },\n {\n // ---- desired (participant-authored; the admin reads) ----------------\n /** The full desired document: who is asking and the entries it wants. */\n getDesired: requestType(\n object({}),\n object({\n /**\n * entryId → desired entry, each carrying its own `consumer`. A\n * per-participant manifest's entries all share one consumer; a hub\n * broker aggregates entries from many consumers in one flat map.\n */\n requested: record(string(), zManifestRequest),\n /** Bumped whenever `requested` changes; lets watchers dedupe ticks. */\n revision: number(),\n }),\n ).withStream({ client: object({}) }),\n /**\n * Coarse change tap on the desired document. Emits an empty tick when\n * `requested` may have changed; the caller re-`getDesired`. Resolves when\n * the caller cancels. Mirrors `hubrpc.directory::watch`.\n */\n watchDesired: requestType(\n object({}),\n object({}),\n ).withStream({ server: object({}) }),\n\n // ---- current / granted (admin-authored; the participant reads) ------\n /** The full current document: entryId → granted/denied (absent ⇒ undecided). */\n getCurrent: requestType(\n object({}),\n object({\n /** entryId → current value (granted | denied). */\n current: record(string(), zCurrentEntry),\n /** Bumped on every successful `setCurrent`. */\n revision: number(),\n }),\n ),\n /**\n * Apply patches to the current document. Replace the whole document with\n * `{ op: 'set', path: '', value }`, or a single entry with\n * `{ op: 'set', path: '/current/<entryId>', value }` (a `zCurrentEntry`:\n * granted or denied). Admin-only in practice (gated by a capability\n * rooted at an accepted issuer).\n */\n setCurrent: requestType(\n object({ patches: array(zManifestPatch) }),\n object({ revision: number() }),\n ).withStream({ client: object({}) }),\n /**\n * Coarse change tap on the current document. Emits an empty tick when\n * `current` may have changed; the participant re-`getCurrent` and applies\n * any new capabilities. Resolves when the caller cancels.\n */\n watchCurrent: requestType(\n object({}),\n object({}),\n ).withStream({ server: object({}) }),\n },\n);\n\n/**\n * The typed client shape of {@link hubAccessManifestInterface} — the exact\n * object `connection.get(hubAccessManifestInterface)` (or\n * `connection.service(id).get(...)`) returns. Approvers depend on this contract,\n * not on a concrete connection: the same approver runs against a local in-memory\n * host (loopback connection), a remote hub's served manifest, or a future\n * aggregating implementation with no code change.\n */\nexport type IHubAccessManifest = InterfaceClient<typeof hubAccessManifestInterface>;\n\n/** One typed entry from `hubAccessManifest::getDesired().requested`. */\nexport type HubAccessManifestRequest =\n Awaited<ReturnType<IHubAccessManifest['getDesired']>>['requested'][string];\n\n/** One typed value accepted at `/current/<entryId>` by `setCurrent`. */\nexport type HubAccessManifestDecision =\n Awaited<ReturnType<IHubAccessManifest['getCurrent']>>['current'][string];\n"],"mappings":"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"}
|