@kehto/shell 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Browser adapter over @kehto/runtime — ShellBridge, domain proxies, keys-forwarder.
4
4
 
5
+ > **Alpha status:** Kehto is an early runtime implementation for a draft NIP-5D
6
+ > protocol. NUB contracts, `supports()` behavior, and shell capabilities are not
7
+ > final; treat this package as current implementation guidance.
8
+
5
9
  ## Install
6
10
 
7
11
  ```bash
@@ -14,11 +18,11 @@ pnpm add @kehto/shell
14
18
 
15
19
  The primary entry point is `createShellBridge()` — it owns the postMessage listener, AUTH handshake, manifest verification, and every dispatch back into the runtime engine.
16
20
 
17
- Canonical v1.2 behaviors this package enforces:
21
+ Current draft behaviors this package enforces:
18
22
 
19
23
  - The shell does not inject a host-provided nostr object into napplets — NIP-5D explicitly forbids napplet-visible signing. Napplets call `relay.publish` / `relay.publishEncrypted` and the shell mediates the signing flow internally (NIP-44 default, NIP-04 opt-in for encrypted envelopes).
20
24
  - `shell.supports(capability)` uses the `perm:<permission>` namespace for sandbox permissions, not the v1.1 bare capability list.
21
- - Five optional per-domain proxies — `createIdentityProxy`, `createThemeProxy`, `createKeysProxy`, `createMediaProxy`, `createNotifyProxy` — can be composed between napplet and runtime to intercept or augment traffic per NUB. They are NOT wired by default (the runtime already owns 8-domain dispatch); they exist as host-app composition seams.
25
+ - Five optional per-domain proxies — `createIdentityProxy`, `createThemeProxy`, `createKeysProxy`, `createMediaProxy`, `createNotifyProxy` — can be composed between napplet and runtime to intercept or augment traffic per NUB. They are NOT wired by default (Kehto's runtime already owns dispatch for the currently supported domains); they exist as host-app composition seams.
22
26
  - The keys-forwarder pumps host keydown events into `keys.forward` envelopes for napplets that hold the `keys:forward` capability.
23
27
 
24
28
  ## Quick Start
@@ -43,24 +47,24 @@ bridge.runtime.registerService(
43
47
  ## Public API
44
48
 
45
49
  ### Bridge factory
46
- - [`createShellBridge`](../../docs/api/functions/_kehto_shell.createShellBridge.html) — primary entry point; returns a `ShellBridge` (exposed `runtime`, `shell.ready`, lifecycle hooks)
50
+ - `createShellBridge` — primary entry point; returns a `ShellBridge` (exposed `runtime`, `shell.ready`, lifecycle hooks)
47
51
  - `ShellBridge` — interface type for the returned bridge
48
52
 
49
53
  ### Hooks adapter
50
- - [`adaptHooks`](../../docs/api/functions/_kehto_shell.adaptHooks.html) — convert a `ShellAdapter` + `BrowserDeps` into the canonical `RuntimeAdapter` hook bag consumed by `@kehto/runtime`
54
+ - `adaptHooks` — convert a `ShellAdapter` + `BrowserDeps` into the canonical `RuntimeAdapter` hook bag consumed by `@kehto/runtime`
51
55
 
52
56
  ### Shell init
53
- - [`buildShellCapabilities`](../../docs/api/functions/_kehto_shell.buildShellCapabilities.html) — construct the `ShellCapabilities` payload emitted during the `shell.ready` / `shell.init` handshake
57
+ - `buildShellCapabilities` — construct the current draft `ShellCapabilities` payload emitted during the `shell.ready` / `shell.init` handshake
54
58
 
55
59
  ### Domain proxies (NIP-5D composition seams)
56
- - [`createIdentityProxy`](../../docs/api/functions/_kehto_shell.createIdentityProxy.html) — intercept `identity.getProfile/getFollows/...` traffic
57
- - [`createThemeProxy`](../../docs/api/functions/_kehto_shell.createThemeProxy.html) — intercept `theme.get/theme.changed`
58
- - [`createKeysProxy`](../../docs/api/functions/_kehto_shell.createKeysProxy.html) — intercept `keys.bind/unbind/bindings`
59
- - [`createMediaProxy`](../../docs/api/functions/_kehto_shell.createMediaProxy.html) — intercept `media.*` playback control
60
- - [`createNotifyProxy`](../../docs/api/functions/_kehto_shell.createNotifyProxy.html) — intercept `notify.send/list/read/dismiss`
60
+ - `createIdentityProxy` — intercept `identity.getProfile/getFollows/...` traffic
61
+ - `createThemeProxy` — intercept `theme.get/theme.changed`
62
+ - `createKeysProxy` — intercept `keys.bind/unbind/bindings`
63
+ - `createMediaProxy` — intercept `media.*` playback control
64
+ - `createNotifyProxy` — intercept `notify.send/list/read/dismiss`
61
65
 
62
66
  ### Keys forwarder
63
- - [`createKeysForwarder`](../../docs/api/functions/_kehto_shell.createKeysForwarder.html) — host-keydown pump into `keys.forward` envelopes; auto-attached by `createShellBridge`, also exported for hosts that manage their own forwarder instance
67
+ - `createKeysForwarder` — host-keydown pump into `keys.forward` envelopes; auto-attached by `createShellBridge`, also exported for hosts that manage their own forwarder instance
64
68
 
65
69
  ### Session / origin registry
66
70
  - `sessionRegistry` — canonical windowId ↔ verified-napplet registry singleton
@@ -88,13 +92,14 @@ Exported for host-app integration: `ShellAdapter`, `ShellCapabilities`, `RelayPo
88
92
 
89
93
  ### Compat re-exports (DRIFT-CORE-06)
90
94
 
91
- Retained for v1.1 migration consumers; new integrations should use canonical NIP-5D envelope types from `@napplet/core`. Slated for removal once upstream restores those exports.
95
+ Retained for migration consumers; new integrations should use current NIP-5D envelope types from `@napplet/core`. Slated for removal once upstream restores those exports.
92
96
 
93
97
  Re-exported from `@kehto/runtime`: the v1.1 bus-kind enum, auth event kind, shell bridge URI constant, protocol version string, the full capability list, destructive-kind set, and the replay window seconds constant. Re-exported types cover the v1.1 capability union and bus-kind numeric union. See the typedoc API reference below for the exact identifier list.
94
98
 
95
99
  ## API Reference
96
100
 
97
- Full API reference: [docs/api/@kehto/shell/](../../docs/api/modules/_kehto_shell.html) (generated via `pnpm docs:api`).
101
+ Full package docs: [`docs/packages/shell.md`](../../docs/packages/shell.md).
102
+ Generated API module: `docs/api/modules/_kehto_shell.html` (run `pnpm docs:api`).
98
103
 
99
104
  ## License
100
105
 
package/dist/index.d.ts CHANGED
@@ -1,45 +1,9 @@
1
- import { Capability, ServiceRegistry, RuntimeConfigOverrides, ConsentRequest, Runtime, RuntimeAdapter } from '@kehto/runtime';
2
- export { ALL_CAPABILITIES, AclChecker, Capability, ConsentRequest, EnforceConfig, EnforceResult, IdentityResolver, NubEnforceConfig, NubMessage, ServiceDescriptor, ServiceHandler, ServiceRegistry, createEnforceGate, createNubEnforceGate, formatDenialReason } from '@kehto/runtime';
1
+ import { Capability, NappletClass, ServiceRegistry, RuntimeConfigOverrides, ConsentRequest, Runtime, SessionEntry, PendingUpdate, RuntimeAdapter } from '@kehto/runtime';
2
+ export { ALL_CAPABILITIES, AclChecker, Capability, ConsentRequest, EnforceConfig, EnforceResult, IdentityResolver, NappKeyEntry, NappletClass, NubEnforceConfig, NubMessage, PendingUpdate, ServiceDescriptor, ServiceHandler, ServiceRegistry, SessionEntry, createEnforceGate, createNubEnforceGate, formatDenialReason } from '@kehto/runtime';
3
3
  import { NappletMessage, NostrEvent, NostrFilter } from '@napplet/core';
4
4
  export { NappletMessage, NostrEvent, NostrFilter, TOPICS, TopicKey, TopicValue } from '@napplet/core';
5
- import { Theme } from '@napplet/nub-theme';
5
+ import { Theme } from '@napplet/nub/theme/types';
6
6
 
7
- /**
8
- * Registry entry mapping a napplet's pubkey to its runtime metadata.
9
- * Created after a successful NIP-42 AUTH handshake or NIP-5D origin registration.
10
- * @example
11
- * ```ts
12
- * const entry: SessionEntry = {
13
- * pubkey: 'abc123...', windowId: 'win-1', origin: '*',
14
- * type: 'chat', dTag: '3chat', aggregateHash: 'deadbeef',
15
- * registeredAt: Date.now(), instanceId: 'guid-123',
16
- * identitySource: 'auth',
17
- * };
18
- * ```
19
- */
20
- interface SessionEntry {
21
- /**
22
- * @deprecated NIP-5D: AUTH keypair no longer exists. Empty string for NIP-5D sessions.
23
- * Kept for backward compatibility during legacy support period.
24
- */
25
- pubkey: string;
26
- windowId: string;
27
- origin: string;
28
- type: string;
29
- dTag: string;
30
- aggregateHash: string;
31
- registeredAt: number;
32
- /** Persistent GUID for this iframe instance, assigned by the runtime. Survives page reloads. */
33
- instanceId: string;
34
- /**
35
- * How session identity was established.
36
- * 'source' = NIP-5D (identity registered at iframe creation via originRegistry).
37
- * 'auth' = legacy AUTH handshake (pubkey is the derived keypair pubkey).
38
- */
39
- identitySource: 'auth' | 'source';
40
- }
41
- /** @deprecated Use SessionEntry. Will be removed in v0.9.0. */
42
- type NappKeyEntry = SessionEntry;
43
7
  /**
44
8
  * ACL entry controlling what a napplet pubkey is permitted to do.
45
9
  * @example
@@ -198,8 +162,9 @@ interface AclCheckEvent {
198
162
  message?: NappletMessage | unknown[];
199
163
  }
200
164
  /**
201
- * Static capability set injected into napplet iframes at creation time.
202
- * Used by `window.napplet.shell.supports()` for synchronous capability queries.
165
+ * Static capability set sent to napplet iframes through the shell.ready /
166
+ * shell.init handshake. Used by hosted `window.napplet.shell.supports()` for
167
+ * synchronous capability queries after the shim consumes shell.init.
203
168
  *
204
169
  * Per canonical NIP-5D (specs/NIP-5D.md lines 81-94), supports() distinguishes
205
170
  * two namespaces:
@@ -216,9 +181,9 @@ interface AclCheckEvent {
216
181
  */
217
182
  interface ShellCapabilities {
218
183
  /**
219
- * NUB domain prefixes the shell handles. Canonical 8-domain set from @napplet/nub-*:
220
- * relay, identity, storage, ifc, theme, keys, media, notify. `relay` is conditional
221
- * on the RelayPoolHooks being provided.
184
+ * NUB domain prefixes the shell handles. Kehto's hosted playground set is:
185
+ * relay, identity, storage, ifc, theme, keys, media, notify, config,
186
+ * resource, connect, class. `relay` is conditional on RelayPoolHooks.
222
187
  *
223
188
  * Entries are bare domain names — `'relay'`, `'identity'`, etc. They MUST NOT
224
189
  * carry the `perm:` prefix; that prefix is reserved for the `sandbox` array.
@@ -272,12 +237,18 @@ interface ShellAdapter {
272
237
  onHashMismatch?: (dTag: string, claimed: string, computed: string) => void;
273
238
  /**
274
239
  * Called at iframe creation for NIP-5D napplets.
275
- * Returns identity metadata for originRegistry.register().
276
- * Return null for non-NIP-5D (legacy) iframes.
240
+ * Returns identity metadata for originRegistry.register(), INCLUDING the
241
+ * class posture (CLASS-01, breaking v1.7). Returning `class: null` selects
242
+ * the permissive default (D2). Returning null overall means "not NIP-5D /
243
+ * skip registration" (unchanged semantics from v1.6).
244
+ *
245
+ * BREAKING v1.7: previously `{ dTag, aggregateHash } | null`; now requires
246
+ * `class` in the non-null branch. See .changeset/class-01-breaking-hook.md.
277
247
  */
278
248
  onNip5dIframeCreate?: (windowId: string) => {
279
249
  dTag: string;
280
250
  aggregateHash: string;
251
+ class: NappletClass;
281
252
  } | null;
282
253
  /**
283
254
  * Optional service extensions. Each key is a service name (e.g., 'audio',
@@ -305,17 +276,144 @@ interface ShellAdapter {
305
276
  }
306
277
 
307
278
  /**
308
- * shell-bridge.ts — Browser adapter over @kehto/runtime.
279
+ * connect-store.ts — NUB-CONNECT grant registry keyed on (dTag, aggregateHash).
280
+ *
281
+ * Mirrors acl-store.ts pattern: module-level singleton, localStorage persistence,
282
+ * composite-key Map. Grants are per-napplet-build: changing the aggregateHash
283
+ * (a rebuild) invalidates prior grants (CONNECT-06) — the composite key makes
284
+ * this structural, not policy-driven.
309
285
  *
310
- * Thin shell that converts browser MessageEvents into windowId-based
311
- * NIP-5D envelope messages for the runtime engine. All protocol logic
312
- * (NUB dispatch, ACL enforcement, subscription lifecycle, signer proxying)
313
- * lives in @kehto/runtime.
286
+ * Default policy is RESTRICTIVE (opposite of aclStore): unknown (dTag, hash, origin)
287
+ * combinations return `false` from check() napplets must be explicitly granted
288
+ * origins via grant(). This is the NUB-CONNECT security invariant.
314
289
  *
315
- * The only browser-specific concern here is extracting the source Window
316
- * from a MessageEvent, mapping it to a windowId via originRegistry, and
317
- * routing shell.ready/shell.init handshake locally.
290
+ * @see packages/shell/src/types/internal-connect.ts for the wire types.
291
+ * @see docs/policies/SHELL-CONNECT-POLICY.md (Plan 39-05) for the full policy.
292
+ */
293
+ /**
294
+ * Public interface for the NUB-CONNECT grant store singleton.
295
+ *
296
+ * All methods are keyed on (dTag, aggregateHash). A rebuild with a new
297
+ * aggregateHash is a distinct identity — CONNECT-06 hash-upgrade semantics
298
+ * are structurally guaranteed: check(dTag, newHash, origin) returns false
299
+ * because newHash has no entry in the store.
300
+ */
301
+ interface ConnectStore {
302
+ /**
303
+ * Check whether an origin has been granted for a specific (dTag, aggregateHash).
304
+ *
305
+ * Returns false for unknown (dTag, aggregateHash) pairs — RESTRICTIVE default.
306
+ * Returns false for unknown origins even if the (dTag, hash) pair exists.
307
+ *
308
+ * @param dTag - The napplet's dTag identifier
309
+ * @param aggregateHash - The napplet's build aggregate hash
310
+ * @param origin - The origin to check (e.g., 'https://relay.example.com')
311
+ * @returns true if origin is in the grant set; false otherwise
312
+ */
313
+ check(dTag: string, aggregateHash: string, origin: string): boolean;
314
+ /**
315
+ * Get all granted origins for a (dTag, aggregateHash) pair.
316
+ *
317
+ * Returns an empty array for unknown (dTag, aggregateHash) pairs.
318
+ *
319
+ * @param dTag - The napplet's dTag identifier
320
+ * @param aggregateHash - The napplet's build aggregate hash
321
+ * @returns Readonly array of granted origin strings; empty if not granted
322
+ */
323
+ getOrigins(dTag: string, aggregateHash: string): readonly string[];
324
+ /**
325
+ * Grant a set of origins to a (dTag, aggregateHash) pair.
326
+ *
327
+ * Deduplicates and sorts origins for idempotent CSP header output (Plan 39-03).
328
+ * Replaces any existing grant for the same (dTag, aggregateHash) pair.
329
+ * Persists to localStorage automatically.
330
+ *
331
+ * @param dTag - The napplet's dTag identifier
332
+ * @param aggregateHash - The napplet's build aggregate hash
333
+ * @param origins - Origins to grant (e.g., ['https://relay.example.com', 'wss://relay2.example.com'])
334
+ */
335
+ grant(dTag: string, aggregateHash: string, origins: readonly string[]): void;
336
+ /**
337
+ * Revoke all granted origins for a (dTag, aggregateHash) pair.
338
+ *
339
+ * After revocation, check() returns false and getOrigins() returns [].
340
+ * Persists to localStorage automatically.
341
+ *
342
+ * @param dTag - The napplet's dTag identifier
343
+ * @param aggregateHash - The napplet's build aggregate hash
344
+ */
345
+ revoke(dTag: string, aggregateHash: string): void;
346
+ /**
347
+ * Get all current grants across all (dTag, aggregateHash) pairs.
348
+ *
349
+ * Used by the Vite CSP plugin (Plan 39-03) to build the connect-src
350
+ * header for each napplet on dev-server startup.
351
+ *
352
+ * @returns Readonly array of all grants
353
+ */
354
+ getAllGrants(): ReadonlyArray<{
355
+ dTag: string;
356
+ aggregateHash: string;
357
+ origins: readonly string[];
358
+ }>;
359
+ /**
360
+ * Persist the current store state to localStorage under 'napplet:connect'.
361
+ *
362
+ * Tolerates unavailable localStorage (e.g., SSR, private browsing with
363
+ * storage disabled). Called automatically by grant() and revoke().
364
+ */
365
+ persist(): void;
366
+ /**
367
+ * Load the store state from localStorage.
368
+ *
369
+ * Validates shape before populating; clears store on corrupt data.
370
+ * Should be called once on shell startup before handling any napplet messages.
371
+ */
372
+ load(): void;
373
+ /**
374
+ * Clear all grants and remove the localStorage entry.
375
+ *
376
+ * Best-effort localStorage removal (tolerates unavailability).
377
+ */
378
+ clear(): void;
379
+ }
380
+ /**
381
+ * NUB-CONNECT grant store singleton.
382
+ *
383
+ * Module-level instance — import and use directly. Persists under localStorage
384
+ * key 'napplet:connect'. Call `connectStore.load()` on shell startup to restore
385
+ * persisted grants from a previous session.
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * import { connectStore } from '@kehto/shell';
390
+ *
391
+ * // On shell startup:
392
+ * connectStore.load();
393
+ *
394
+ * // On consent approval:
395
+ * connectStore.grant(dTag, aggregateHash, ['https://relay.example.com']);
396
+ *
397
+ * // In Vite CSP plugin (Plan 39-03):
398
+ * const origins = connectStore.getOrigins(dTag, aggregateHash);
399
+ * ```
400
+ */
401
+ declare const connectStore: ConnectStore;
402
+ /**
403
+ * Compose the canonical `<dTag>:<aggregateHash>` composite key used by the
404
+ * connect-store. Exported for consumers that need to key their own maps by
405
+ * the same identity (e.g., Vite CSP plugin — Plan 39-03).
406
+ *
407
+ * @param dTag - The napplet's dTag identifier
408
+ * @param aggregateHash - The napplet's build aggregate hash
409
+ * @returns Composite key string in `<dTag>:<aggregateHash>` format
410
+ *
411
+ * @example
412
+ * ```ts
413
+ * const key = connectGrantKey('chat', 'abc123'); // 'chat:abc123'
414
+ * ```
318
415
  */
416
+ declare function connectGrantKey(dTag: string, aggregateHash: string): string;
319
417
 
320
418
  /**
321
419
  * Shell-side message bridge that handles NIP-5D communication with napplet iframes.
@@ -354,17 +452,19 @@ interface ShellBridge {
354
452
  */
355
453
  handleMessage(event: MessageEvent): void;
356
454
  /**
357
- * Inject a shell-originated event into subscription delivery.
455
+ * Inject a shell-originated event into subscription delivery. Under NIP-5D,
456
+ * shell-originated events are forwarded to napplets as ifc.event envelope
457
+ * messages. The runtime's injectEvent() handles the per-session routing.
358
458
  *
359
- * Under NIP-5D, shell-originated events are forwarded to napplets as
360
- * ifc.event envelope messages. The runtime's injectEvent() handles
361
- * the per-session routing.
459
+ * v1.10 hard-removed the v1.8 soft-rename compatibility branch for the
460
+ * old `auth:identity-changed` topic. Use the canonical `identity:changed`
461
+ * topic for identity-change pushes.
362
462
  *
363
- * @param topic - The event topic tag value (e.g., 'auth:identity-changed')
463
+ * @param topic - The event topic tag value. Forwarded exactly once.
364
464
  * @param payload - The event content
365
465
  * @example
366
466
  * ```ts
367
- * bridge.injectEvent('auth:identity-changed', { pubkey: userPubkey });
467
+ * bridge.injectEvent('identity:changed', { pubkey: userPubkey });
368
468
  * ```
369
469
  */
370
470
  injectEvent(topic: string, payload: unknown): void;
@@ -417,12 +517,36 @@ interface ShellBridge {
417
517
  * ```
418
518
  */
419
519
  publishTheme(theme: Theme): void;
520
+ /**
521
+ * Publish the current shell-user identity to every loaded napplet.
522
+ *
523
+ * Posts an `identity.changed` envelope (shell → napplet push) with the
524
+ * current user pubkey. An empty pubkey means no signer/user identity is
525
+ * currently connected. This is distinct from NIP-5D napplet session identity,
526
+ * which remains source-bound at iframe creation.
527
+ *
528
+ * @param pubkey - Current user's hex pubkey, or empty string when signed out.
529
+ */
530
+ publishIdentityChanged(pubkey: string): void;
420
531
  /**
421
532
  * Access the underlying runtime instance for advanced use cases.
422
533
  * Provides direct access to the runtime's sessionRegistry, aclState,
423
534
  * and manifestCache.
424
535
  */
425
536
  readonly runtime: Runtime;
537
+ /**
538
+ * Access the NUB-CONNECT grant store. Per-napplet connect grants are
539
+ * keyed on (dTag, aggregateHash) and persisted under localStorage key
540
+ * 'napplet:connect'. Public API surface for the Vite CSP middleware
541
+ * (Plan 39-03) and the consent flow (Plan 39-04).
542
+ *
543
+ * Default policy is RESTRICTIVE: check() returns false for any origin
544
+ * not explicitly granted. Call load() on shell startup to restore
545
+ * persisted grants from a previous session.
546
+ *
547
+ * @see SHELL-CONNECT-POLICY.md for the full policy.
548
+ */
549
+ readonly connectStore: ConnectStore;
426
550
  }
427
551
  /**
428
552
  * Create a ShellBridge instance with dependency injection via hooks.
@@ -451,13 +575,6 @@ interface ShellBridge {
451
575
  */
452
576
  declare function createShellBridge(hooks: ShellAdapter): ShellBridge;
453
577
 
454
- /**
455
- * Origin Registry — Window reference to windowId mapping.
456
- *
457
- * Used by the ShellBridge to validate that postMessage senders are known
458
- * napplet iframes. event.source (a Window reference) is the only unforgeable
459
- * origin — never trust event.origin from the message payload.
460
- */
461
578
  /**
462
579
  * Bidirectional registry mapping Window references to windowId strings.
463
580
  * Optionally stores NIP-5D identity metadata (dTag and aggregateHash) per window.
@@ -592,16 +709,6 @@ declare const manifestCache: {
592
709
  clear(): void;
593
710
  };
594
711
 
595
- /**
596
- * ACL Store — composite-keyed capability registry for napplet identity.
597
- *
598
- * ACL entries are keyed by dTag:aggregateHash — a 2-segment composite key
599
- * that ties permissions to a specific napplet build (NIP-5D format).
600
- * Old 3-segment keys (pubkey:dTag:hash) are automatically migrated via migrateAclState().
601
- *
602
- * Default policy is PERMISSIVE: unknown identities have all capabilities granted.
603
- */
604
-
605
712
  /**
606
713
  * ACL store — manages capability grants, revocations, and blocks for napp identities.
607
714
  * Persists to localStorage and uses a permissive default policy (all capabilities granted).
@@ -806,25 +913,6 @@ declare const audioManager: {
806
913
  * verified pubkey here. Both mappings are kept in sync.
807
914
  */
808
915
 
809
- /**
810
- * A pending napplet update — raised when a napplet reconnects with a different aggregateHash.
811
- * @example
812
- * ```ts
813
- * const update: PendingUpdate = {
814
- * windowId: 'win-1', pubkey: 'abc...', dTag: '3chat',
815
- * oldHash: 'aaa', newHash: 'bbb',
816
- * resolve: (action) => { if (action === 'accept') { // apply } },
817
- * };
818
- * ```
819
- */
820
- interface PendingUpdate {
821
- windowId: string;
822
- pubkey: string;
823
- dTag: string;
824
- oldHash: string;
825
- newHash: string;
826
- resolve: (action: 'accept' | 'block') => void;
827
- }
828
916
  /**
829
917
  * Bidirectional registry mapping windowIds to verified napplet pubkeys.
830
918
  * Maintained by ShellBridge after successful AUTH handshakes.
@@ -1024,25 +1112,13 @@ interface BrowserDeps {
1024
1112
  */
1025
1113
  declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): RuntimeAdapter;
1026
1114
 
1027
- /**
1028
- * shell-init.ts — Shell initialization utilities.
1029
- *
1030
- * Provides buildShellCapabilities() — derives the shell's static NUB capability
1031
- * set from the ShellAdapter configuration, used in the shell.ready / shell.init
1032
- * handshake so napplets can query shell.supports() synchronously.
1033
- *
1034
- * Canonical NIP-5D forbids the shell from injecting a NIP-07 proxy object on
1035
- * the napplet iframe's global scope (specs/NIP-5D.md line 44 + Security §6).
1036
- * This module therefore does NOT expose any signing proxy to napplet code.
1037
- * Signing/encryption is mediated by the shell through relay.publish and
1038
- * relay.publishEncrypted — never via a napplet-visible API surface.
1039
- */
1040
-
1041
1115
  /**
1042
1116
  * Build the shell's static capability set from adapter configuration.
1043
1117
  *
1044
- * NUB capabilities = canonical 8-domain list from @napplet/nub-*:
1045
- * relay (gated on hooks.relayPool), identity, storage, ifc, theme, keys, media, notify.
1118
+ * NUB capabilities = Kehto-hosted domain list from @napplet/nub subpaths,
1119
+ * plus relay (gated on hooks.relayPool):
1120
+ * relay (gated on hooks.relayPool), identity, storage, ifc, theme,
1121
+ * keys, media, notify, config, resource, connect, class.
1046
1122
  *
1047
1123
  * Sandbox permissions are left empty by default — host apps may extend after
1048
1124
  * construction. Sandbox entries returned here (and any host-app extensions)
@@ -1056,7 +1132,7 @@ declare function adaptHooks(shellHooks: ShellAdapter, deps: BrowserDeps): Runtim
1056
1132
  * @example
1057
1133
  * ```ts
1058
1134
  * const caps = buildShellCapabilities(hooks);
1059
- * // caps.nubs => ['relay','identity','storage','ifc','theme','keys','media','notify']
1135
+ * // caps.nubs => ['relay','identity','storage','ifc','theme','keys','media','notify','config','resource','connect','class','cvm']
1060
1136
  * // (relay present when hooks.relayPool is provided; bare names only)
1061
1137
  * // caps.sandbox => [] // host app may extend with 'perm:popups', etc.
1062
1138
  * ```
@@ -1346,7 +1422,7 @@ declare function createKeysProxy(deps: KeysProxyDeps): KeysProxy;
1346
1422
  /**
1347
1423
  * media-proxy.ts — Shell-side per-domain proxy for media.* envelopes.
1348
1424
  *
1349
- * Establishes the shell-side composition seam for `@napplet/nub-media`
1425
+ * Establishes the shell-side composition seam for `@napplet/nub/media`
1350
1426
  * session-control envelopes. Shape mirrors identity-proxy (Plan 12-11):
1351
1427
  *
1352
1428
  * - `dispatch(windowId, envelope)` routes napplet→shell media requests
@@ -1434,7 +1510,7 @@ declare function createMediaProxy(deps: MediaProxyDeps): MediaProxy;
1434
1510
  /**
1435
1511
  * notify-proxy.ts — Shell-side per-domain proxy for notify.* envelopes.
1436
1512
  *
1437
- * Establishes the shell-side composition seam for `@napplet/nub-notify`
1513
+ * Establishes the shell-side composition seam for `@napplet/nub/notify`
1438
1514
  * notification envelopes. Shape mirrors identity-proxy (Plan 12-11):
1439
1515
  *
1440
1516
  * - `dispatch(windowId, envelope)` routes napplet→shell notify requests
@@ -1524,7 +1600,7 @@ declare function createNotifyProxy(deps: NotifyProxyDeps): NotifyProxy;
1524
1600
  * to registered napplets as `keys.forward` envelopes (Plan 12-11, NUB-05
1525
1601
  * shell-side half).
1526
1602
  *
1527
- * Per `@napplet/nub-keys`, `keys.forward` is fire-and-forget (no result
1603
+ * Per `@napplet/nub/keys`, `keys.forward` is fire-and-forget (no result
1528
1604
  * envelope, no correlation id). Field names follow the nub convention:
1529
1605
  * `{ ctrl, alt, shift, meta }` — NOT the DOM-style `ctrlKey`/etc.
1530
1606
  *
@@ -1614,4 +1690,147 @@ interface KeysForwarder {
1614
1690
  */
1615
1691
  declare function createKeysForwarder(deps: KeysForwarderDeps): KeysForwarder;
1616
1692
 
1617
- export { type AclCheckEvent, type AclEntry, type AudioSource, type AuthHooks, type BrowserDeps, type ConfigHooks, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type KeysForwarder, type KeysForwarderDeps, type KeysForwarderOriginRegistry, type KeysForwarderSessionRegistry, type KeysProxy, type KeysProxyDeps, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NappKeyEntry, type NotifyProxy, type NotifyProxyDeps, type PendingUpdate, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type SessionEntry, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ThemeProxy, type ThemeProxyDeps, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildShellCapabilities, createIdentityProxy, createKeysForwarder, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, manifestCache, nappKeyRegistry, originRegistry, sessionRegistry };
1693
+ /**
1694
+ * @file internal-connect.ts
1695
+ *
1696
+ * Kehto-internal shell-side connect-store + consent-flow types. Per
1697
+ * PROJECT.md Decision #31, this is NOT a staging-ground duplicate of upstream
1698
+ * `@napplet/nub/connect`: upstream exports a napplet-side accessor interface
1699
+ * (`NappletConnect = { granted, origins }`) plus the shared
1700
+ * `normalizeConnectOrigin` pure validator. Kehto's types here describe the
1701
+ * shell's grant-store records (`ConnectGrant`, `ConnectGrantKey`,
1702
+ * `ConsentResult`), used by the connect-store singleton and the Vite CSP
1703
+ * plugin's consent flow.
1704
+ *
1705
+ * The two surfaces are complementary: upstream's accessor type ships at the
1706
+ * napplet boundary; kehto's grant-store types live shell-side. No retirement
1707
+ * planned. For canonical origin validation (kehto has no local impl;
1708
+ * Decision #32), consume `@napplet/nub/connect`'s `normalizeConnectOrigin`
1709
+ * directly.
1710
+ *
1711
+ * Downstream consumers (Phase 39 `connect-store.ts`, Vite CSP middleware,
1712
+ * consent flow UI) import from this module.
1713
+ */
1714
+ /**
1715
+ * Grant key composed from the napplet's dTag and build aggregateHash. A hash
1716
+ * upgrade invalidates prior grants (CONNECT-06).
1717
+ */
1718
+ interface ConnectGrantKey {
1719
+ dTag: string;
1720
+ aggregateHash: string;
1721
+ }
1722
+ /**
1723
+ * A persisted connect grant. Stored in the shell connect-store singleton,
1724
+ * surfaced as the CSP `connect-src` origin list for the corresponding napplet.
1725
+ */
1726
+ interface ConnectGrant {
1727
+ /** Composite key `"<dTag>:<aggregateHash>"` under which this grant is stored. */
1728
+ key: string;
1729
+ /** The set of origins (WebSocket and HTTPS) the napplet is granted to connect to. */
1730
+ origins: readonly string[];
1731
+ /** Epoch millis when the grant was approved by the user. */
1732
+ grantedAt: number;
1733
+ }
1734
+ /**
1735
+ * Consent flow outcome. `'dismiss'` resolves to deny (MUST NOT default to allow).
1736
+ * `'timeout'` also resolves to deny.
1737
+ */
1738
+ type ConsentResult = 'approve' | 'deny' | 'dismiss' | 'timeout';
1739
+ /**
1740
+ * Inbound consent request from a napplet requesting connect access to one or
1741
+ * more origins.
1742
+ */
1743
+ interface ConnectConsentRequest {
1744
+ dTag: string;
1745
+ aggregateHash: string;
1746
+ /** Origins the napplet is asking to be granted access to. */
1747
+ requestedOrigins: readonly string[];
1748
+ }
1749
+
1750
+ /**
1751
+ * @file internal-resource.ts
1752
+ *
1753
+ * Kehto-internal shell-side resource wire types. Per PROJECT.md Decision #31,
1754
+ * this is NOT a staging-ground duplicate of upstream `@napplet/nub/resource`:
1755
+ * Phase 44 audit confirmed the two surfaces diverge substantively — different
1756
+ * field names (kehto's `requestId` vs upstream `id`; kehto's `bodyBase64` vs
1757
+ * upstream `blob` + `mime`), different message-type names
1758
+ * (`ResourceBytesRequest` vs `ResourceBytesMessage`), and disjoint error
1759
+ * vocabularies (kehto: 5 codes `{denied, canceled, network-error, invalid-url,
1760
+ * class-forbidden}`; upstream: 8 codes `{not-found, blocked-by-policy,
1761
+ * timeout, too-large, unsupported-scheme, decode-failed, network-error,
1762
+ * quota-exceeded}`).
1763
+ *
1764
+ * Future migration to upstream's surface is its own phase. For now this file
1765
+ * owns the wire shapes kehto's `resource-service.ts` and resource-demo napplet
1766
+ * already implement.
1767
+ *
1768
+ * Canonical 4-message protocol (RESOURCE-03):
1769
+ * Inbound: resource.bytes, resource.cancel
1770
+ * Outbound: resource.bytes.result, resource.bytes.error
1771
+ */
1772
+ /**
1773
+ * Unique id for correlating a `resource.bytes` request to its later result /
1774
+ * error / cancel envelope.
1775
+ */
1776
+ type ResourceRequestId = string;
1777
+ /**
1778
+ * Inbound: napplet requests bytes from an origin. Shell consults
1779
+ * getConnectGrants(dTag, aggregateHash) before proxying; ungranted origins
1780
+ * receive a `denied` error (RESOURCE-01 H-03 prevention).
1781
+ */
1782
+ interface ResourceBytesRequest {
1783
+ type: 'resource.bytes';
1784
+ requestId: ResourceRequestId;
1785
+ url: string;
1786
+ /** Optional subset of fetch init (method, headers). Body bytes are shell-proxy-internal. */
1787
+ init?: {
1788
+ method?: string;
1789
+ headers?: Readonly<Record<string, string>>;
1790
+ };
1791
+ }
1792
+ /**
1793
+ * Inbound: napplet cancels a previously-issued bytes request. Shell correlates
1794
+ * to the in-flight request by requestId and emits a `resource.bytes.error`
1795
+ * with `code: 'canceled'`.
1796
+ */
1797
+ interface ResourceCancelRequest {
1798
+ type: 'resource.cancel';
1799
+ requestId: ResourceRequestId;
1800
+ }
1801
+ /**
1802
+ * Outbound: successful fetch result.
1803
+ */
1804
+ interface ResourceBytesResult {
1805
+ type: 'resource.bytes.result';
1806
+ requestId: ResourceRequestId;
1807
+ status: number;
1808
+ headers: Readonly<Record<string, string>>;
1809
+ /** Raw response bytes, base64-encoded for the postMessage wire. */
1810
+ bodyBase64: string;
1811
+ }
1812
+ /**
1813
+ * Canonical typed-error codes for RESOURCE-03 cancel correlation + H-03
1814
+ * ungranted-origin refusal.
1815
+ */
1816
+ type ResourceErrorCode = 'denied' | 'canceled' | 'network-error' | 'invalid-url' | 'class-forbidden';
1817
+ /**
1818
+ * Outbound: error result — used for both grant-refusal (RESOURCE-01) and
1819
+ * cancel-correlation (RESOURCE-03) cases.
1820
+ */
1821
+ interface ResourceBytesError {
1822
+ type: 'resource.bytes.error';
1823
+ requestId: ResourceRequestId;
1824
+ code: ResourceErrorCode;
1825
+ message: string;
1826
+ }
1827
+ /**
1828
+ * Union of all inbound resource wire messages (napplet -> shell).
1829
+ */
1830
+ type ResourceInbound = ResourceBytesRequest | ResourceCancelRequest;
1831
+ /**
1832
+ * Union of all outbound resource wire messages (shell -> napplet).
1833
+ */
1834
+ type ResourceOutbound = ResourceBytesResult | ResourceBytesError;
1835
+
1836
+ export { type AclCheckEvent, type AclEntry, type AudioSource, type AuthHooks, type BrowserDeps, type ConfigHooks, type ConnectConsentRequest, type ConnectGrant, type ConnectGrantKey, type ConnectStore, type ConsentResult, type CryptoHooks, type DmHooks, type HotkeyHooks, type IdentityProxy, type IdentityProxyDeps, type KeysForwarder, type KeysForwarderDeps, type KeysForwarderOriginRegistry, type KeysForwarderSessionRegistry, type KeysProxy, type KeysProxyDeps, type ManifestCacheEntry, type MediaProxy, type MediaProxyDeps, type NotifyProxy, type NotifyProxyDeps, type ProxyOriginRegistry, type RelayConfigHooks, type RelayPoolHooks, type RelayPoolLike, type ResourceBytesError, type ResourceBytesRequest, type ResourceBytesResult, type ResourceCancelRequest, type ResourceErrorCode, type ResourceInbound, type ResourceOutbound, type ResourceRequestId, type ShellAdapter, type ShellBridge, type ShellCapabilities, type ThemeProxy, type ThemeProxyDeps, type WindowManagerHooks, type WorkerRelayHooks, type WorkerRelayLike, adaptHooks, audioManager, buildShellCapabilities, connectGrantKey, connectStore, createIdentityProxy, createKeysForwarder, createKeysProxy, createMediaProxy, createNotifyProxy, createShellBridge, createThemeProxy, manifestCache, nappKeyRegistry, originRegistry, sessionRegistry };