@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,481 @@
1
+ import "./nodeTransit-cmtZgdpW.js";
2
+ import { Et as Hub, Ot as IDisposable, Tt as AttachedLink } from "./hubConnectionAcceptor-BwydvFa4.js";
3
+ import { AcceptedRootIssuer, Base64Sha256, Capability, IMessageTransport, Identity, Keypair, LinkRpcConnection, ManagedIdentityStorageBackend, Permission, PrincipalId, PublicSigningIdentity, SignedCapability, SigningIdentity, X25519Keypair } from "@hediet/linkrpc";
4
+ import { ServiceId } from "@hediet/linkrpc/hub/common";
5
+ //#region src/hub/server/routing/forwardingTable.d.ts
6
+ /**
7
+ * A longest-prefix routing table keyed by {@link ServiceId} prefixes — the
8
+ * hub's equivalent of an IP forwarding table.
9
+ *
10
+ * A prefix `"a/b"` owns every serviceId equal to it or beneath it
11
+ * (`"a/b"`, `"a/b/c"`, …) *unless* a longer claimed prefix matches first.
12
+ * Matching is segment-aware: `"a/bc"` is **not** under `"a/b"`.
13
+ *
14
+ * The table is intentionally a plain stateful container with no opinion on
15
+ * *who* may claim what — that policy lives in {@link Hub.claimPrefix}. This
16
+ * keeps the data structure trivially testable in isolation.
17
+ */
18
+ declare class ForwardingTable<T> {
19
+ private readonly _entries;
20
+ /**
21
+ * Reverse index: value → the set of prefixes claimed for it. Kept in
22
+ * lock-step with {@link _entries} so {@link deleteByValue} (used when a
23
+ * link detaches) is O(claims-for-that-value) instead of an O(size) scan.
24
+ */
25
+ private readonly _prefixesByValue;
26
+ /** Number of claimed prefixes. */
27
+ get size(): number;
28
+ has(prefix: ServiceId): boolean;
29
+ get(prefix: ServiceId): T | undefined;
30
+ /**
31
+ * Claim `prefix` for `value`. Overwrites any existing claim — callers
32
+ * that want claim-once semantics must check {@link has} first (the hub
33
+ * does).
34
+ */
35
+ set(prefix: ServiceId, value: T): void;
36
+ delete(prefix: ServiceId): boolean;
37
+ /**
38
+ * Remove every claim bound to `value` (matched by reference/equality).
39
+ * O(number of prefixes that value owns). Returns the removed prefixes.
40
+ */
41
+ deleteByValue(value: T): ServiceId[];
42
+ private _dropFromIndex;
43
+ entries(): IterableIterator<[ServiceId, T]>;
44
+ prefixes(): ServiceId[];
45
+ /**
46
+ * Find the value owning the longest claimed prefix of `serviceId`.
47
+ * Walks up the `'/'` segments — `"a/b/c"` tries `"a/b/c"`, `"a/b"`,
48
+ * `"a"` in that order — and returns the first hit, or `undefined` if
49
+ * none of its ancestors are claimed.
50
+ */
51
+ longestPrefixMatch(serviceId: ServiceId): {
52
+ prefix: ServiceId;
53
+ value: T;
54
+ } | undefined;
55
+ }
56
+ /**
57
+ * Validate a forwarding-table **prefix**: a non-root {@link ServiceId}. The
58
+ * root (`""`) is a valid service id but cannot be claimed as a prefix (it
59
+ * would capture every address); the uplink is the route of last resort
60
+ * instead. Returns an error string, or `undefined` when well-formed.
61
+ */
62
+ declare function validatePrefix(prefix: unknown): string | undefined;
63
+ //#endregion
64
+ //#region src/hub/server/hubRegisterServiceId.d.ts
65
+ /**
66
+ * Register an **in-process** service on `hub` under `serviceId`, returning a
67
+ * {@link RegisteredServiceId} whose `connection` serves its interfaces.
68
+ *
69
+ * This is the hubv2 replacement for the v1 `hub.attachParticipant(pair.a)` +
70
+ * `LinkRpcConnection.fromTransport(pair.b)` + `enableReflection({ serviceId })`
71
+ * dance that every built-in extension service performed. It:
72
+ *
73
+ * 1. attaches an in-memory link to the hub and claims `serviceId` on it, so
74
+ * the hub routes every fully-qualified `serviceId::…` call to this service;
75
+ * 2. exposes a {@link LinkRpcConnection} whose registered interfaces answer
76
+ * those calls (register them with `{ serviceId }`); and
77
+ * 3. enables reflection under `serviceId` so the hub's aggregating
78
+ * `directory::list` surfaces this service.
79
+ *
80
+ * Disposing the returned handle (`svc.dispose()`) closes the connection and
81
+ * detaches the link, releasing the claimed prefix.
82
+ *
83
+ * Unlike an accepted *participant* (which gets a {@link RootOverlay} and only
84
+ * reaches the hub through its uplink), an in-process service is attached
85
+ * directly to the hub as a prefix owner. It is fully trusted — no provenance,
86
+ * identity, or forwarded-call gating applies to traffic it receives.
87
+ */
88
+ declare function hubRegisterServiceId(hub: Hub, serviceId: ServiceId): RegisteredServiceId;
89
+ /**
90
+ * Handle to an in-process service registered on a {@link Hub} via
91
+ * {@link hubRegisterServiceId}. Owns both the {@link LinkRpcConnection} the
92
+ * service serves its interfaces on and the hub {@link AttachedLink} that routes
93
+ * traffic to it. {@link dispose} closes the connection and detaches the link
94
+ * (releasing the claimed prefix) — wire it into the owning feature's disposal.
95
+ */
96
+ declare class RegisteredServiceId implements IDisposable {
97
+ /** Serve the service's interfaces here (register them with `{ serviceId }`). */
98
+ readonly connection: LinkRpcConnection;
99
+ private readonly _link;
100
+ constructor(
101
+ /** Serve the service's interfaces here (register them with `{ serviceId }`). */
102
+ connection: LinkRpcConnection, _link: AttachedLink);
103
+ dispose(): void;
104
+ }
105
+ //#endregion
106
+ //#region src/hub/server/mintCapability.d.ts
107
+ interface MintCapabilityOptions {
108
+ /** The admin identity that issues (signs) the capability. */
109
+ readonly issuer: SigningIdentity;
110
+ /** The holder allowed to wield the capability. */
111
+ readonly audience: PrincipalId;
112
+ /** What the holder may do. A call matches iff it matches any permission. */
113
+ readonly permissions: readonly Permission[];
114
+ /** Unix milliseconds. Omit for a capability that never expires. */
115
+ readonly expiresAtMs?: number;
116
+ /**
117
+ * Per-cap distinguisher (base64url). Defaults to a fresh random 128-bit
118
+ * value.
119
+ */
120
+ readonly nonce?: string;
121
+ /** Delegation parent: the content hash (`signedHash("capability", parent)`) of the parent capability. */
122
+ readonly parentHash?: Base64Sha256<Capability>;
123
+ }
124
+ /**
125
+ * Assemble and sign a {@link Capability} with the admin identity.
126
+ *
127
+ * This replaces the v1 `hub.proposeCapability` / `signProposedCapability`
128
+ * pair: the consent engine decides the permissions (e.g. from
129
+ * {@link resolveAccessCandidates} + the user's consent selection) and mints
130
+ * the capability bound to the consumer's PrincipalId as `audience`.
131
+ */
132
+ declare function mintCapability(options: MintCapabilityOptions): Promise<SignedCapability>;
133
+ //#endregion
134
+ //#region src/hub/server/verifiedSignature.d.ts
135
+ /**
136
+ * Wrap an inbound transport so every request's `$hubrpc` signature is verified
137
+ * **eagerly, at the door** — a pure authenticity gate with no capability check:
138
+ *
139
+ * - a **valid** signature → the request is delivered unchanged;
140
+ * - an **invalid** signature → the request is **rejected** here (an error
141
+ * response is sent and the message is never delivered);
142
+ * - an **unsigned** call (no envelope) → delivered unchanged, so keyless
143
+ * connections still work.
144
+ *
145
+ * This is the signature half of the hub's trust model. *Authorization*
146
+ * (capability chains) is **not** this wrapper's job — that is enforced
147
+ * separately by {@link import('./forwardedCallGate').withForwardedCallGate} on
148
+ * the hub-facing (forwarded) path. The root overlay uses this wrapper on its own
149
+ * root-form calls (consent / identity front doors), which never pass through the
150
+ * forwarded-call gate and so need their authenticity established here.
151
+ *
152
+ * When `verifySignatures` is not `true`, the transport is returned unchanged (no
153
+ * verification, every message passes verbatim).
154
+ *
155
+ * Verification is async but in-order delivery is preserved via a per-link
156
+ * promise chain.
157
+ */
158
+ declare function withVerifiedSignature(link: IMessageTransport, options?: {
159
+ readonly verifySignatures?: boolean;
160
+ }): IMessageTransport;
161
+ //#endregion
162
+ //#region src/hub/server/forwardedCallGate.d.ts
163
+ /** Options common to both gate modes (authenticity-only and capability). */
164
+ interface ForwardedCallGateBaseOptions {
165
+ /**
166
+ * ServiceId prefixes whose calls pass **verbatim**, unverified — an
167
+ * opt-in escape hatch. **Empty by default**: reflection and forwarded
168
+ * service calls are gated like any other. The consent front door
169
+ * (`hubAccess::*`) is served at the connection root (never forwarded), so it
170
+ * is reached directly and never passes through this gate.
171
+ */
172
+ readonly exemptPrefixes?: Iterable<string>;
173
+ /** Override the clock (Unix milliseconds) used for skew / expiry checks. Testing seam. */
174
+ readonly nowMs?: () => number;
175
+ }
176
+ /**
177
+ * Per-service trust anchors, consulted with the call's `serviceId`. A
178
+ * capability authorises only if its chain roots at one of the anchors returned
179
+ * for that service, so a participant cannot self-issue authority. An empty
180
+ * result rejects every capability (fail closed) — there is deliberately no
181
+ * "accept any root" affordance. Anchors flagged `isPublic` are named in
182
+ * rejection messages.
183
+ */
184
+ type AcceptedRootIssuerResolver = (serviceId: string) => readonly AcceptedRootIssuer[];
185
+ /**
186
+ * Gate configuration. A **discriminated union on `requireCapability`** so the
187
+ * type system enforces the one combination that is actually safe:
188
+ *
189
+ * - **authenticity only** (`requireCapability` omitted / `false`): a valid
190
+ * `$hubrpc` signature is enough; *authorization* is left to the target
191
+ * service / capability layer. `acceptedRootIssuers` is optional here.
192
+ * - **capability mode** (`requireCapability: true`): a bare signature is not
193
+ * enough — the request must also present a capability that {@link permits}
194
+ * the concrete call, rooted at an accepted issuer. Because there is no safe
195
+ * default trust anchor, either fixed `trustedRoots` or a service-specific
196
+ * `acceptedRootIssuers` resolver is **required at compile time**. This makes
197
+ * it impossible to request enforcement while silently forgetting whose
198
+ * capabilities to trust.
199
+ */
200
+ type ForwardedCallGateOptions = (ForwardedCallGateBaseOptions & {
201
+ readonly requireCapability?: false;
202
+ readonly acceptedRootIssuers?: AcceptedRootIssuerResolver;
203
+ readonly trustedRoots?: never;
204
+ }) | (ForwardedCallGateBaseOptions & {
205
+ readonly requireCapability: true;
206
+ } & ({
207
+ readonly acceptedRootIssuers: AcceptedRootIssuerResolver;
208
+ readonly trustedRoots?: never;
209
+ } | {
210
+ /** Fixed public trust roots accepted for every forwarded service. */
211
+ readonly trustedRoots: readonly PublicSigningIdentity[];
212
+ readonly acceptedRootIssuers?: never;
213
+ }));
214
+ /**
215
+ * A **signature front door** for fully-qualified calls. Wrap an incoming
216
+ * transport with this before attaching it to a hub or constructing the serving
217
+ * connection, so every `serviceId::interfaceId::member` call must carry a
218
+ * valid `$hubrpc` signature.
219
+ * Unsigned or tampered calls are rejected with an error response and never
220
+ * reach the downstream hub or connection.
221
+ *
222
+ * What passes **verbatim** (ungated):
223
+ * - responses;
224
+ * - root-addressed requests (`interfaceId::member`) and bare requests
225
+ * (`member`) — these always terminate at the connection root and are never
226
+ * forwarded; and
227
+ * - requests targeting an {@link ForwardedCallGateOptions.exemptPrefixes exempt
228
+ * prefix} (the hub's own services, which gate themselves).
229
+ *
230
+ * The gate **does not strip** the envelope: signed wire params keep the real
231
+ * call params at the top level alongside `$hubrpc`/`$hubrpcUnsigned`, so the
232
+ * target's typed handler recovers them via schema-stripping while a target that
233
+ * cares may re-verify as defense-in-depth. Authorization is *not* this gate's
234
+ * job (unless {@link ForwardedCallGateOptions.requireCapability} is set): it
235
+ * proves *who* is calling, leaving *whether they may* to the capability layer.
236
+ *
237
+ * In-order delivery toward the hub is preserved across the async verification:
238
+ * inbound messages are processed through a single serial queue.
239
+ */
240
+ declare function withForwardedCallGate(inner: IMessageTransport, options: ForwardedCallGateOptions): IMessageTransport;
241
+ /** Explicitly named alias for {@link withForwardedCallGate}. */
242
+ declare function withFullyQualifiedCallGate(inner: IMessageTransport, options: ForwardedCallGateOptions): IMessageTransport;
243
+ //#endregion
244
+ //#region src/hub/server/identityKeystore.d.ts
245
+ /**
246
+ * On-disk encrypted keystore for managed identities. State is keyed by
247
+ * {@link SlotId} (the absolute path to the entry HTML / Node entry point).
248
+ * Each slot owns up to four files under `storageDir`, mode `0o600`,
249
+ * AES-256-GCM-sealed under `keystoreSecret`:
250
+ *
251
+ * - `<hash>.bin` — the managed identity keypair
252
+ * - `<hash>.storage.bin` — the per-slot persistent KV store
253
+ * - `<hash>.keys.bin` — the access-key allow-list (gate)
254
+ * - `<hash>.snooze.bin` — the consent re-prompt suppression (optional)
255
+ * - `<hash>.filesdir.bin`— the recorded path of the plaintext app-files
256
+ * folder (optional; the folder itself lives
257
+ * wherever the owner placed it, NOT here)
258
+ *
259
+ * `<hash>` is `sha256(id).slice(0,16)`. The slot id is bound into each
260
+ * file's AEAD AAD so swapping files between slots fails the auth tag.
261
+ *
262
+ * The `keystoreSecret` itself is opaque bytes — the keystore does not
263
+ * know or care where it came from. In the extension, it's loaded from
264
+ * `vscode.SecretStorage`.
265
+ */
266
+ interface IdentityKeystoreOptions {
267
+ /** Absolute directory path. Created if missing. */
268
+ readonly storageDir: string;
269
+ /** 32+ bytes of secret material used to seal identity files. */
270
+ readonly keystoreSecret: Uint8Array;
271
+ }
272
+ /**
273
+ * Stable slot identity: the absolute path to the entry HTML / Node entry
274
+ * point. One slot ⇒ one managed identity + one storage backend, shared
275
+ * across every approved {@link SlotAccessKey}.
276
+ */
277
+ type SlotId = string;
278
+ /**
279
+ * An access key: a small, exact-match qualifier tuple a caller must
280
+ * present to reach a slot's identity/storage. Conventionally
281
+ * `{ appId, bundleHash }`, but the keystore treats it opaquely. Compared
282
+ * by canonical (sorted-field) JSON equality.
283
+ */
284
+ type SlotAccessKey = Readonly<Record<string, string>>;
285
+ /**
286
+ * Result of `slotByIdAndKey`. Either the resolved
287
+ * slot, or a discriminated error:
288
+ * - `slotDoesNotExist` — no slot has ever been created for this id.
289
+ * - `unknownKey` — the slot exists but `key` is not in its
290
+ * allow-list (a different/unapproved bundle).
291
+ */
292
+ type SlotByIdAndKeyResult = IdentitySlot | {
293
+ readonly error: "slotDoesNotExist";
294
+ } | {
295
+ readonly error: "unknownKey";
296
+ };
297
+ /**
298
+ * Result of `slotByIdAndTime`. Either the resolved slot (access is currently
299
+ * time-authorized) or a single discriminated error:
300
+ * - `consentRequired` — the slot holds inheritable state and no active
301
+ * snooze covers the supplied time, so the caller
302
+ * must obtain fresh consent before granting identity.
303
+ */
304
+ type SlotByIdAndTimeResult = IdentitySlot | {
305
+ readonly error: "consentRequired";
306
+ };
307
+ /**
308
+ * A time-boxed, scoped suppression of identity-access consent re-prompts for
309
+ * a slot. Set from the consent dialog's "don't ask for file changes for 24h"
310
+ * checkbox; read on each load to decide whether a content change can load
311
+ * silently. Cleared by {@link IdentitySlot.delete}.
312
+ *
313
+ * - `scope: "entry"` — covers entry-HTML content changes only.
314
+ * - `scope: "all"` — covers entry-HTML AND external-file content changes.
315
+ * - `expiresAt` — absolute epoch ms; expired snoozes are ignored.
316
+ *
317
+ * A snooze never suppresses a *new external path* — that is always a fresh
318
+ * blast-radius decision the user must see.
319
+ */
320
+ interface IdentitySnooze {
321
+ readonly scope: "entry" | "all";
322
+ readonly expiresAt: number;
323
+ }
324
+ interface IdentitySlot {
325
+ readonly id: SlotId;
326
+ /** The slot's managed identity; generated + persisted on first call. */
327
+ getOrCreateIdentity(): Promise<Identity>;
328
+ /** Existing identity or `undefined` — never creates. */
329
+ peekIdentity(): Promise<Identity | undefined>;
330
+ /** Per-slot persistent KV (caps + app data). File created on first write. */
331
+ readonly storage: ManagedIdentityStorageBackend;
332
+ /**
333
+ * Absolute path of the slot's *plaintext* app-files folder, or
334
+ * `undefined` if none has been recorded yet. The keystore only stores
335
+ * and wipes this opaque string — the owner (the extension) chooses the
336
+ * path (typically `<globalStorage>/app-files/<appId>` with a collision
337
+ * counter) and performs the actual file IO.
338
+ *
339
+ * Recording the path in the slot is deliberate: the folder name is not
340
+ * derivable from the app id alone (the counter breaks that), so an app
341
+ * can only reach its own folder by looking it up through its slot. This
342
+ * both isolates apps from each other and prevents accidental access to a
343
+ * sibling's folder.
344
+ */
345
+ getFilesDir(): Promise<string | undefined>;
346
+ /** Record the chosen absolute path of this slot's app-files folder. */
347
+ setFilesDir(absPath: string): Promise<void>;
348
+ /**
349
+ * `true` iff a files folder is recorded AND it currently holds ≥1 entry.
350
+ * Counts toward {@link hasState} so a content change to a privileged app
351
+ * still trips the consent gate even when the app stored only files (and
352
+ * never created an identity or KV state).
353
+ */
354
+ hasFiles(): Promise<boolean>;
355
+ /**
356
+ * Delete every entry inside the recorded files folder but keep the
357
+ * folder itself and its slot record (so the path stays stable for the
358
+ * user). No-op when no folder is recorded. Used by the consent dialog's
359
+ * "wipe app files" option on a *keep-identity* load.
360
+ */
361
+ wipeFiles(): Promise<void>;
362
+ /**
363
+ * `true` iff there is anything a consumer could inherit: an identity
364
+ * has been created, storage holds ≥1 entry, OR the app-files folder
365
+ * holds ≥1 entry. When `false`, granting access exposes nothing — the
366
+ * consent prompt can be skipped safely.
367
+ */
368
+ hasState(): Promise<boolean>;
369
+ /** All keys currently allowed to access this slot. */
370
+ listKeys(): Promise<SlotAccessKey[]>;
371
+ /** `true` iff `key` (exact canonical match) is in the allow-list. */
372
+ hasKey(key: SlotAccessKey): Promise<boolean>;
373
+ /** Add keys to the allow-list (creating the slot if needed). Idempotent. */
374
+ addKeys(...keys: SlotAccessKey[]): Promise<void>;
375
+ /** Remove one key. Returns `true` iff it was present. */
376
+ deleteKey(key: SlotAccessKey): Promise<boolean>;
377
+ /**
378
+ * The active, unexpired snooze for this slot, or `undefined`. Lazily
379
+ * treats an expired snooze as absent (does not rewrite the file).
380
+ */
381
+ getSnooze(): Promise<IdentitySnooze | undefined>;
382
+ /** Persist a snooze for this slot (overwrites any previous one). */
383
+ setSnooze(snooze: IdentitySnooze): Promise<void>;
384
+ /** Remove any snooze for this slot. Idempotent. */
385
+ clearSnooze(): Promise<void>;
386
+ /** Wipe identity, storage, AND all access keys for this slot. */
387
+ delete(): Promise<void>;
388
+ }
389
+ interface IdentityKeystore {
390
+ /**
391
+ * Resolve the slot for `id`, enforcing the access gate. Returns the
392
+ * slot on success, or a discriminated error if the slot does not exist
393
+ * or `key` is not in its allow-list. Use this on the privileged path
394
+ * (registering `identity::*` on an iframe overlay) so an unapproved
395
+ * bundle can never reach prior state.
396
+ */
397
+ slotByIdAndKey(id: SlotId, key: SlotAccessKey): Promise<SlotByIdAndKeyResult>;
398
+ /**
399
+ * Resolve the slot for `id`, gated on *time* rather than a content key.
400
+ * Returns the slot when access is currently authorized — either the slot
401
+ * holds nothing inheritable ({@link IdentitySlot.hasState} is `false`, so
402
+ * granting exposes nothing) or an active, unexpired snooze covers `nowMs`.
403
+ * Otherwise returns `consentRequired`, meaning the slot holds state the
404
+ * caller must obtain fresh consent for. Used on the privileged path for
405
+ * URL apps, which have no content key to present to {@link slotByIdAndKey}
406
+ * but may inherit the slot's identity under a time-boxed grant.
407
+ */
408
+ slotByIdAndTime(id: SlotId, nowMs: number): Promise<SlotByIdAndTimeResult>;
409
+ /**
410
+ * Create the slot for `id` (if missing) and add `key` to its
411
+ * allow-list. Idempotent: re-creating an existing slot just ensures the
412
+ * key is present. Returns the resolved slot. This is the only way to
413
+ * bootstrap a brand-new slot before {@link slotByIdAndKey} can succeed.
414
+ */
415
+ createSlotWithKey(id: SlotId, key: SlotAccessKey): Promise<IdentitySlot>;
416
+ /**
417
+ * Unsafe: return the slot handle for `id` with NO key check. Used to
418
+ * inspect `hasState`/`listKeys` and to run the consent decision before
419
+ * a key has been added. Never register the identity returned from here
420
+ * without having checked/added a key.
421
+ */
422
+ slotById(id: SlotId): IdentitySlot;
423
+ }
424
+ declare function createIdentityKeystore(opts: IdentityKeystoreOptions): IdentityKeystore;
425
+ //#endregion
426
+ //#region src/hub/server/sqliteIdentityKeystore.d.ts
427
+ type DatabaseSync = import("node:sqlite").DatabaseSync;
428
+ /**
429
+ * SQLite-backed {@link IdentityKeystore} — a drop-in alternative to the
430
+ * file-per-slot {@link import('./identityKeystore').createIdentityKeystore}.
431
+ * All slot state (identity keypair, per-slot KV storage, the access-key gate,
432
+ * the consent snooze, and the recorded app-files folder) lives in **one**
433
+ * database file across three tables, so the whole keystore is a single
434
+ * `DatabaseSync` connection instead of four files per slot.
435
+ *
436
+ * The model is identical to the file keystore — `id` (the slot), `keys` (the
437
+ * gate), and `time` (the snooze) — even though config/CLI hubs only exercise
438
+ * the `id` + storage axes today. Keeping the full surface means the extension
439
+ * and node-runner can adopt this backend unchanged later.
440
+ *
441
+ * ## Encryption seam
442
+ *
443
+ * This backend currently runs in **unencrypted mode**: secret-bearing columns
444
+ * (identity private keys, storage values) are stored plaintext via the
445
+ * identity {@link _codec}. When at-rest encryption is added, swap {@link _codec}
446
+ * for an AEAD codec keyed by a `keystoreSecret` with `slotId` bound into the
447
+ * AAD (mirroring the file keystore's per-file AAD). The columns stay `TEXT`
448
+ * (ciphertext is base64url), so enabling encryption is a codec swap plus a
449
+ * one-time re-encode — not a schema migration.
450
+ *
451
+ * Uses the built-in `node:sqlite` `DatabaseSync`, matching the monorepo's
452
+ * other SQLite stores. The module is loaded only when a keystore opens a
453
+ * database, so consumers that only import the Hub package do not receive
454
+ * Node's SQLite experimental warning.
455
+ */
456
+ interface SqliteIdentitySlot extends IdentitySlot {
457
+ /**
458
+ * Write a **specific** keypair into this slot iff it has no identity yet,
459
+ * and return the resulting identity. Used to import a legacy plaintext
460
+ * identity while preserving its principal. If an identity already exists
461
+ * it is returned unchanged (the import is a no-op).
462
+ */
463
+ importIdentity(ed: Keypair, wrap: X25519Keypair): Promise<Identity>;
464
+ }
465
+ interface SqliteIdentityKeystore extends IdentityKeystore {
466
+ slotById(id: SlotId): SqliteIdentitySlot;
467
+ }
468
+ interface SqliteIdentityKeystoreOptions {
469
+ /**
470
+ * Path to the SQLite database file (created if missing) or `":memory:"`.
471
+ * Ignored when {@link db} is provided.
472
+ */
473
+ readonly dbPath?: string;
474
+ /** An existing connection to reuse (share one file across stores). */
475
+ readonly db?: DatabaseSync;
476
+ }
477
+ /** Open (creating if needed) a SQLite-backed {@link IdentityKeystore}. */
478
+ declare function createSqliteIdentityKeystore(opts: SqliteIdentityKeystoreOptions): SqliteIdentityKeystore;
479
+ //#endregion
480
+ export { validatePrefix as C, ForwardingTable as S, withVerifiedSignature as _, IdentityKeystore as a, RegisteredServiceId as b, IdentitySnooze as c, SlotByIdAndTimeResult as d, SlotId as f, withFullyQualifiedCallGate as g, withForwardedCallGate as h, createSqliteIdentityKeystore as i, SlotAccessKey as l, ForwardedCallGateOptions as m, SqliteIdentityKeystoreOptions as n, IdentityKeystoreOptions as o, createIdentityKeystore as p, SqliteIdentitySlot as r, IdentitySlot as s, SqliteIdentityKeystore as t, SlotByIdAndKeyResult as u, MintCapabilityOptions as v, hubRegisterServiceId as x, mintCapability as y };
481
+ //# sourceMappingURL=index-CLIUrV88.d.ts.map