@kehto/acl 0.11.0 → 0.13.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
@@ -21,7 +21,7 @@ Every function is pure: state in, state out. No I/O, no timers, no globals — t
21
21
  The module exposes two parallel capability surfaces:
22
22
 
23
23
  - **Bitfield constants** (`CAP_RELAY_READ`, `CAP_RELAY_WRITE`, `CAP_STATE_READ`, `CAP_STATE_WRITE`, …) — the compact per-entry representation used inside `AclState.entries[*].caps`.
24
- - **Current draft NIP-5D capability strings** (`CAP_IDENTITY_READ`, `CAP_KEYS_BIND`, `CAP_KEYS_FORWARD`, `CAP_MEDIA_CONTROL`, `CAP_NOTIFY_SEND`, `CAP_NOTIFY_CHANNEL`, `CAP_THEME_READ`) — plus the retained `relay:*`, `cache:*`, `hotkey:forward`, and `state:*` literals. These strings are what `resolveCapabilitiesNub()` returns and what `@kehto/runtime`'s enforce gate grants/revokes against. The earlier `sign:event`/`sign:nip04`/`sign:nip44` entries were intentionally removed — the current NIP-5D draft does not expose napplet-visible signing.
24
+ - **Current draft NIP-5D capability strings** (`CAP_IDENTITY_READ`, `CAP_KEYS_BIND`, `CAP_KEYS_FORWARD`, `CAP_MEDIA_CONTROL`, `CAP_NOTIFY_SEND`, `CAP_NOTIFY_CHANNEL`, `CAP_THEME_READ`) — plus the retained `relay:*`, `cache:*`, `hotkey:forward`, and `state:*` literals. These strings are what `resolveCapabilitiesNap()` returns and what `@kehto/runtime`'s enforce gate grants/revokes against. The earlier `sign:event`/`sign:nip04`/`sign:nip44` entries were intentionally removed — the current NIP-5D draft does not expose napplet-visible signing.
25
25
 
26
26
  ## Quick Start
27
27
 
@@ -63,8 +63,8 @@ check(state, id, CAP_RELAY_WRITE); // true (restored)
63
63
  - `AclEntry` — per-identity entry (`caps`, `blocked`, `quota`)
64
64
  - `Identity` — `{ dTag, hash }` pair (NIP-5D 2-segment identity)
65
65
  - `Capability` — union of every canonical capability string
66
- - `CapabilityResolution` — `{ senderCap, recipientCap }` returned by `resolveCapabilitiesNub`
67
- - `NubMessage` — minimal shape consumed by `resolveCapabilitiesNub` (`{ type: string }`)
66
+ - `CapabilityResolution` — `{ senderCap, recipientCap }` returned by `resolveCapabilitiesNap`
67
+ - `NapMessage` — minimal shape consumed by `resolveCapabilitiesNap` (`{ type: string }`)
68
68
 
69
69
  ### Constants — bit flags
70
70
  - `CAP_RELAY_READ`, `CAP_RELAY_WRITE`, `CAP_CACHE_READ`, `CAP_CACHE_WRITE`
@@ -87,7 +87,7 @@ check(state, id, CAP_RELAY_WRITE); // true (restored)
87
87
  ### Capability resolution
88
88
  - `check` — evaluate identity + capability against state
89
89
  - `toKey` — compute the `dTag:hash` composite key
90
- - `resolveCapabilitiesNub` — map a NIP-5D NAP envelope type to the required sender/recipient capabilities across the current supported domains
90
+ - `resolveCapabilitiesNap` — map a NIP-5D NAP envelope type to the required sender/recipient capabilities across the current supported domains
91
91
 
92
92
  ### Migration
93
93
  - `migrateAclState` — one-shot migration from the legacy 3-segment `pubkey:dTag:hash` keys to the v1.2 2-segment `dTag:hash` keys; idempotent (returns the same reference when nothing to migrate)
@@ -2,7 +2,7 @@
2
2
  * All capability strings recognized by @kehto/acl.
3
3
  *
4
4
  * Ordering: v1.1 surface first (relay/cache/hotkey/state), then the
5
- * v1.2 additions for the seven nubs + theme. The v1.1 `sign:event`,
5
+ * v1.2 additions for the seven naps + theme. The v1.1 `sign:event`,
6
6
  * `sign:nip04`, `sign:nip44` strings were intentionally removed — no
7
7
  * napplet-visible signing exists in canonical NIP-5D; signing flows
8
8
  * through shell-internal `relay.publishEncrypted` instead.
@@ -26,7 +26,7 @@ declare const CAP_NOTIFY_CHANNEL: "notify:channel";
26
26
  declare const CAP_THEME_READ: "theme:read";
27
27
  /** config.get / config.subscribe / config.unsubscribe / config.registerSchema / config.openSettings */
28
28
  declare const CAP_CONFIG_READ: "config:read";
29
- /** resource.bytes / resource.cancel (inbound) + resource.bytes.result / resource.bytes.error (outbound) */
29
+ /** resource.bytes / resource.bytesMany / resource.cancel (inbound) + resource result/error pushes */
30
30
  declare const CAP_RESOURCE_FETCH: "resource:fetch";
31
31
  /** cvm.discover / cvm.request / cvm.close (inbound) + cvm.*.result / cvm.event (outbound) */
32
32
  declare const CAP_CVM_CALL: "cvm:call";
@@ -15,7 +15,7 @@ import {
15
15
  CAP_RESOURCE_FETCH,
16
16
  CAP_THEME_READ,
17
17
  CAP_UPLOAD_WRITE
18
- } from "./chunk-RNHAOBS6.js";
18
+ } from "./chunk-6GH7LZSY.js";
19
19
  export {
20
20
  ALL_CAPABILITIES,
21
21
  CAP_CONFIG_READ,
@@ -8,7 +8,7 @@ var ALL_CAPABILITIES = [
8
8
  "hotkey:forward",
9
9
  "state:read",
10
10
  "state:write",
11
- // v1.2 additions (seven nubs + theme):
11
+ // v1.2 additions (seven naps + theme):
12
12
  "identity:read",
13
13
  "keys:bind",
14
14
  "keys:forward",
@@ -16,9 +16,9 @@ var ALL_CAPABILITIES = [
16
16
  "notify:send",
17
17
  "notify:channel",
18
18
  "theme:read",
19
- // v1.7 Phase 39 — NUB-CONFIG reference service (9th domain):
19
+ // v1.7 Phase 39 — NAP-CONFIG reference service (9th domain):
20
20
  "config:read",
21
- // v1.7 Phase 40 — NUB-RESOURCE reference service (10th domain):
21
+ // v1.7 Phase 40 — NAP-RESOURCE reference service (10th domain):
22
22
  "resource:fetch",
23
23
  // NAP-CVM — ContextVM bridge (11th domain): call MCP-over-Nostr servers.
24
24
  "cvm:call",
@@ -70,4 +70,4 @@ export {
70
70
  CAP_INTENT_READ,
71
71
  CAP_INTENT_WRITE
72
72
  };
73
- //# sourceMappingURL=chunk-RNHAOBS6.js.map
73
+ //# sourceMappingURL=chunk-6GH7LZSY.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/capabilities.ts"],"sourcesContent":["\n/**\n * All capability strings recognized by @kehto/acl.\n *\n * Ordering: v1.1 surface first (relay/cache/hotkey/state), then the\n * v1.2 additions for the seven nubs + theme. The v1.1 `sign:event`,\n * `sign:nip04`, `sign:nip44` strings were intentionally removed — no\n * napplet-visible signing exists in canonical NIP-5D; signing flows\n * through shell-internal `relay.publishEncrypted` instead.\n */\nexport const ALL_CAPABILITIES = [\n // v1.1 kept:\n 'relay:read', 'relay:write',\n 'cache:read', 'cache:write',\n 'hotkey:forward',\n 'state:read', 'state:write',\n // v1.2 additions (seven nubs + theme):\n 'identity:read',\n 'keys:bind', 'keys:forward',\n 'media:control',\n 'notify:send', 'notify:channel',\n 'theme:read',\n // v1.7 Phase 39 — NUB-CONFIG reference service (9th domain):\n 'config:read',\n // v1.7 Phase 40 — NUB-RESOURCE reference service (10th domain):\n 'resource:fetch',\n // NAP-CVM — ContextVM bridge (11th domain): call MCP-over-Nostr servers.\n 'cvm:call',\n // NAP-OUTBOX — outbox-aware relay routing (12th domain): read = query/\n // subscribe/resolveRelays/close; write = publish (shell-signed fanout).\n 'outbox:read', 'outbox:write',\n // NAP-UPLOAD — shell-mediated file/blob upload (13th domain): a single write\n // cap gates the network-egress + identity-linking upload op; status queries\n // ride the same grant (a napplet only inspects its own uploads).\n 'upload:write',\n // NAP-INTENT — archetype intent dispatch (14th domain): read = available/\n // handlers introspection (and receipt of shell pushes); write = invoke (the\n // focus-stealing cross-napplet dispatch op).\n 'intent:read', 'intent:write',\n] as const;\n\n/** Union of every capability string in ALL_CAPABILITIES. */\nexport type Capability = typeof ALL_CAPABILITIES[number];\n\n/** identity.getProfile/getFollows/getList/getZaps/getMutes/getBlocked/getBadges */\nexport const CAP_IDENTITY_READ = 'identity:read' as const;\n/** keys.registerAction / keys.unregisterAction / keys.bindings */\nexport const CAP_KEYS_BIND = 'keys:bind' as const;\n/** keys.forward / keys.action */\nexport const CAP_KEYS_FORWARD = 'keys:forward' as const;\n/** media.* (all actions) */\nexport const CAP_MEDIA_CONTROL = 'media:control' as const;\n/** notify.send / notify.dismiss / notify.badge / notify.action / notify.clicked / notify.dismissed / notify.controls / notify.send.result */\nexport const CAP_NOTIFY_SEND = 'notify:send' as const;\n/** notify.channel.register / notify.permission.request / notify.permission.result */\nexport const CAP_NOTIFY_CHANNEL = 'notify:channel' as const;\n/** theme.get / theme.changed */\nexport const CAP_THEME_READ = 'theme:read' as const;\n/** config.get / config.subscribe / config.unsubscribe / config.registerSchema / config.openSettings */\nexport const CAP_CONFIG_READ = 'config:read' as const;\n/** resource.bytes / resource.cancel (inbound) + resource.bytes.result / resource.bytes.error (outbound) */\nexport const CAP_RESOURCE_FETCH = 'resource:fetch' as const;\n/** cvm.discover / cvm.request / cvm.close (inbound) + cvm.*.result / cvm.event (outbound) */\nexport const CAP_CVM_CALL = 'cvm:call' as const;\n/** outbox.query / outbox.subscribe / outbox.close / outbox.resolveRelays (read-side outbox access) */\nexport const CAP_OUTBOX_READ = 'outbox:read' as const;\n/** outbox.publish (shell-signed, outbox-aware publish fanout) */\nexport const CAP_OUTBOX_WRITE = 'outbox:write' as const;\n/** upload.upload / upload.status (shell-mediated file/blob upload + status query) */\nexport const CAP_UPLOAD_WRITE = 'upload:write' as const;\n/** intent.available / intent.handlers (read-side archetype introspection + push receipt) */\nexport const CAP_INTENT_READ = 'intent:read' as const;\n/** intent.invoke (focus-stealing cross-napplet archetype dispatch) */\nexport const CAP_INTENT_WRITE = 'intent:write' as const;\n"],"mappings":";AAUO,IAAM,mBAAmB;AAAA;AAAA,EAE9B;AAAA,EAAc;AAAA,EACd;AAAA,EAAc;AAAA,EACd;AAAA,EACA;AAAA,EAAc;AAAA;AAAA,EAEd;AAAA,EACA;AAAA,EAAa;AAAA,EACb;AAAA,EACA;AAAA,EAAe;AAAA,EACf;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA,EAGA;AAAA,EAAe;AAAA;AAAA;AAAA;AAAA,EAIf;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EAAe;AACjB;AAMO,IAAM,oBAAsB;AAE5B,IAAM,gBAAsB;AAE5B,IAAM,mBAAsB;AAE5B,IAAM,oBAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,qBAAsB;AAE5B,IAAM,iBAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,qBAAsB;AAE5B,IAAM,eAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,mBAAsB;AAE5B,IAAM,mBAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,mBAAsB;","names":[]}
1
+ {"version":3,"sources":["../src/capabilities.ts"],"sourcesContent":["\n/**\n * All capability strings recognized by @kehto/acl.\n *\n * Ordering: v1.1 surface first (relay/cache/hotkey/state), then the\n * v1.2 additions for the seven naps + theme. The v1.1 `sign:event`,\n * `sign:nip04`, `sign:nip44` strings were intentionally removed — no\n * napplet-visible signing exists in canonical NIP-5D; signing flows\n * through shell-internal `relay.publishEncrypted` instead.\n */\nexport const ALL_CAPABILITIES = [\n // v1.1 kept:\n 'relay:read', 'relay:write',\n 'cache:read', 'cache:write',\n 'hotkey:forward',\n 'state:read', 'state:write',\n // v1.2 additions (seven naps + theme):\n 'identity:read',\n 'keys:bind', 'keys:forward',\n 'media:control',\n 'notify:send', 'notify:channel',\n 'theme:read',\n // v1.7 Phase 39 — NAP-CONFIG reference service (9th domain):\n 'config:read',\n // v1.7 Phase 40 — NAP-RESOURCE reference service (10th domain):\n 'resource:fetch',\n // NAP-CVM — ContextVM bridge (11th domain): call MCP-over-Nostr servers.\n 'cvm:call',\n // NAP-OUTBOX — outbox-aware relay routing (12th domain): read = query/\n // subscribe/resolveRelays/close; write = publish (shell-signed fanout).\n 'outbox:read', 'outbox:write',\n // NAP-UPLOAD — shell-mediated file/blob upload (13th domain): a single write\n // cap gates the network-egress + identity-linking upload op; status queries\n // ride the same grant (a napplet only inspects its own uploads).\n 'upload:write',\n // NAP-INTENT — archetype intent dispatch (14th domain): read = available/\n // handlers introspection (and receipt of shell pushes); write = invoke (the\n // focus-stealing cross-napplet dispatch op).\n 'intent:read', 'intent:write',\n] as const;\n\n/** Union of every capability string in ALL_CAPABILITIES. */\nexport type Capability = typeof ALL_CAPABILITIES[number];\n\n/** identity.getProfile/getFollows/getList/getZaps/getMutes/getBlocked/getBadges */\nexport const CAP_IDENTITY_READ = 'identity:read' as const;\n/** keys.registerAction / keys.unregisterAction / keys.bindings */\nexport const CAP_KEYS_BIND = 'keys:bind' as const;\n/** keys.forward / keys.action */\nexport const CAP_KEYS_FORWARD = 'keys:forward' as const;\n/** media.* (all actions) */\nexport const CAP_MEDIA_CONTROL = 'media:control' as const;\n/** notify.send / notify.dismiss / notify.badge / notify.action / notify.clicked / notify.dismissed / notify.controls / notify.send.result */\nexport const CAP_NOTIFY_SEND = 'notify:send' as const;\n/** notify.channel.register / notify.permission.request / notify.permission.result */\nexport const CAP_NOTIFY_CHANNEL = 'notify:channel' as const;\n/** theme.get / theme.changed */\nexport const CAP_THEME_READ = 'theme:read' as const;\n/** config.get / config.subscribe / config.unsubscribe / config.registerSchema / config.openSettings */\nexport const CAP_CONFIG_READ = 'config:read' as const;\n/** resource.bytes / resource.bytesMany / resource.cancel (inbound) + resource result/error pushes */\nexport const CAP_RESOURCE_FETCH = 'resource:fetch' as const;\n/** cvm.discover / cvm.request / cvm.close (inbound) + cvm.*.result / cvm.event (outbound) */\nexport const CAP_CVM_CALL = 'cvm:call' as const;\n/** outbox.query / outbox.subscribe / outbox.close / outbox.resolveRelays (read-side outbox access) */\nexport const CAP_OUTBOX_READ = 'outbox:read' as const;\n/** outbox.publish (shell-signed, outbox-aware publish fanout) */\nexport const CAP_OUTBOX_WRITE = 'outbox:write' as const;\n/** upload.upload / upload.status (shell-mediated file/blob upload + status query) */\nexport const CAP_UPLOAD_WRITE = 'upload:write' as const;\n/** intent.available / intent.handlers (read-side archetype introspection + push receipt) */\nexport const CAP_INTENT_READ = 'intent:read' as const;\n/** intent.invoke (focus-stealing cross-napplet archetype dispatch) */\nexport const CAP_INTENT_WRITE = 'intent:write' as const;\n"],"mappings":";AAUO,IAAM,mBAAmB;AAAA;AAAA,EAE9B;AAAA,EAAc;AAAA,EACd;AAAA,EAAc;AAAA,EACd;AAAA,EACA;AAAA,EAAc;AAAA;AAAA,EAEd;AAAA,EACA;AAAA,EAAa;AAAA,EACb;AAAA,EACA;AAAA,EAAe;AAAA,EACf;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA,EAGA;AAAA,EAAe;AAAA;AAAA;AAAA;AAAA,EAIf;AAAA;AAAA;AAAA;AAAA,EAIA;AAAA,EAAe;AACjB;AAMO,IAAM,oBAAsB;AAE5B,IAAM,gBAAsB;AAE5B,IAAM,mBAAsB;AAE5B,IAAM,oBAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,qBAAsB;AAE5B,IAAM,iBAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,qBAAsB;AAE5B,IAAM,eAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,mBAAsB;AAE5B,IAAM,mBAAsB;AAE5B,IAAM,kBAAsB;AAE5B,IAAM,mBAAsB;","names":[]}
package/dist/index.d.ts CHANGED
@@ -323,16 +323,16 @@ declare function deserialize(json: string): AclState;
323
323
  declare function migrateAclState(state: AclState): AclState;
324
324
 
325
325
  /**
326
- * @kehto/acl — NUB/NAP domain capability resolution (8 canonical + config,
326
+ * @kehto/acl — NAP/NAP domain capability resolution (8 canonical + config,
327
327
  * resource, cvm, outbox, upload, intent).
328
328
  *
329
- * Maps NUB message types (e.g., 'relay.subscribe', 'identity.getProfile') to
329
+ * Maps NAP message types (e.g., 'relay.subscribe', 'identity.getProfile') to
330
330
  * the capability strings required by sender and recipient. This is the
331
- * canonical source for "which capability does this NUB operation require?"
331
+ * canonical source for "which capability does this NAP operation require?"
332
332
  * in the @kehto/acl package.
333
333
  *
334
334
  * Canonical NIP-5D 8 domains: identity, keys, media, notify, relay,
335
- * storage, ifc, theme. Extended in v1.7 with: config (Phase 39, 9th domain),
335
+ * storage, inc, theme. Extended in v1.7 with: config (Phase 39, 9th domain),
336
336
  * resource (Phase 40, 10th domain). The v1.1 `signer` domain is REMOVED —
337
337
  * getPublicKey/getRelays migrated to `identity`; signEvent/nip04/nip44 have
338
338
  * no napplet-visible surface (shell handles encryption inside
@@ -341,7 +341,7 @@ declare function migrateAclState(state: AclState): AclState;
341
341
  * Zero dependencies. No imports from @napplet/core or any external package.
342
342
  *
343
343
  * @see packages/acl/src/capabilities.ts for cap string constants + ALL_CAPABILITIES.
344
- * @see docs/ACL-MIGRATION.md section 2 — Capability Constant to NUB Domain Mapping.
344
+ * @see docs/ACL-MIGRATION.md section 2 — Capability Constant to NAP Domain Mapping.
345
345
  */
346
346
  /**
347
347
  * Minimal message shape used for capability resolution.
@@ -349,13 +349,13 @@ declare function migrateAclState(state: AclState): AclState;
349
349
  * Compatible with NappletMessage from @napplet/core, but defined here
350
350
  * independently to maintain @kehto/acl's zero-dependency constraint.
351
351
  *
352
- * @param type - NUB message type, e.g. 'relay.subscribe', 'identity.getProfile'
352
+ * @param type - NAP message type, e.g. 'relay.subscribe', 'identity.getProfile'
353
353
  */
354
- interface NubMessage {
354
+ interface NapMessage {
355
355
  readonly type: string;
356
356
  }
357
357
  /**
358
- * Result of resolving what capabilities a NUB message requires.
358
+ * Result of resolving what capabilities a NAP message requires.
359
359
  *
360
360
  * | Field | Description |
361
361
  * |----------------|----------------------------------------------------------------|
@@ -370,12 +370,12 @@ interface CapabilityResolution {
370
370
  readonly recipientCap: string | null;
371
371
  }
372
372
  /**
373
- * Resolve the capabilities required by a NUB message.
373
+ * Resolve the capabilities required by a NAP message.
374
374
  *
375
375
  * Splits `msg.type` on '.' to obtain `[domain, action]`, then dispatches to
376
376
  * a per-domain mapper. Unknown domains return `null/null` (silently ignored).
377
377
  *
378
- * **NUB domain mapping table (8 canonical domains):**
378
+ * **NAP domain mapping table (8 canonical domains):**
379
379
  *
380
380
  * | Domain | Action(s) | senderCap | recipientCap |
381
381
  * |------------|--------------------------------------------------------------|-----------------|---------------|
@@ -392,14 +392,14 @@ interface CapabilityResolution {
392
392
  * | `storage` | `get`, `keys` | `state:read` | `null` |
393
393
  * | `storage` | `set`, `remove` | `state:write` | `null` |
394
394
  * | `storage` | any other (incl. removed `clear`) | `null` | `null` |
395
- * | `ifc`/`inc` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |
396
- * | `ifc`/`inc` | `subscribe`, `unsubscribe`, `channel.open/list/close` | `relay:read` | `null` |
395
+ * | `inc` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |
396
+ * | `inc` | `subscribe`, `unsubscribe`, `channel.open/list/close` | `relay:read` | `null` |
397
397
  * | `theme` | `get`, `get.result` | `theme:read` | `null` |
398
398
  * | `theme` | `changed` (shell → napplet push) | `null` | `theme:read` |
399
399
  * | `config` | `get`, `subscribe`, `unsubscribe`, `registerSchema`, `openSettings` | `config:read` | `null` |
400
400
  * | `config` | `values`, `registerSchema.result`, `schemaError` (shell → napplet pushes) | `null` | `config:read` |
401
- * | `resource` | `bytes`, `cancel` (napplet → shell requests) | `resource:fetch`| `null` |
402
- * | `resource` | `bytes.result`, `bytes.error` (shell → napplet pushes) | `null` | `resource:fetch` |
401
+ * | `resource` | `bytes`, `bytesMany`, `cancel` (napplet → shell requests) | `resource:fetch`| `null` |
402
+ * | `resource` | `bytes*.result`, `bytes*.error` (shell → napplet pushes) | `null` | `resource:fetch` |
403
403
  * | `intent` | `invoke` (napplet → shell) | `intent:write` | `null` |
404
404
  * | `intent` | `available`, `handlers` (napplet → shell) | `intent:read` | `null` |
405
405
  * | `intent` | `changed`, `*.result`, `*.error` (shell → napplet pushes) | `null` | `intent:read` |
@@ -410,33 +410,33 @@ interface CapabilityResolution {
410
410
  * `identity`; napplet-visible signing does not exist in NIP-5D (shell
411
411
  * signs internally for `relay.publishEncrypted`).
412
412
  *
413
- * @param msg - Message with a `type` field in NUB format (e.g., 'relay.subscribe')
413
+ * @param msg - Message with a `type` field in NAP format (e.g., 'relay.subscribe')
414
414
  * @returns CapabilityResolution with senderCap and recipientCap (each may be null)
415
415
  *
416
416
  * @example
417
417
  * ```ts
418
- * resolveCapabilitiesNub({ type: 'relay.subscribe' })
418
+ * resolveCapabilitiesNap({ type: 'relay.subscribe' })
419
419
  * // => { senderCap: 'relay:read', recipientCap: null }
420
420
  *
421
- * resolveCapabilitiesNub({ type: 'relay.publishEncrypted' })
421
+ * resolveCapabilitiesNap({ type: 'relay.publishEncrypted' })
422
422
  * // => { senderCap: 'relay:write', recipientCap: null }
423
423
  *
424
- * resolveCapabilitiesNub({ type: 'identity.getProfile' })
424
+ * resolveCapabilitiesNap({ type: 'identity.getProfile' })
425
425
  * // => { senderCap: 'identity:read', recipientCap: null }
426
426
  *
427
- * resolveCapabilitiesNub({ type: 'keys.forward' })
427
+ * resolveCapabilitiesNap({ type: 'keys.forward' })
428
428
  * // => { senderCap: 'keys:forward', recipientCap: null }
429
429
  *
430
- * resolveCapabilitiesNub({ type: 'ifc.channel.broadcast' })
430
+ * resolveCapabilitiesNap({ type: 'inc.channel.broadcast' })
431
431
  * // => { senderCap: 'relay:write', recipientCap: 'relay:read' }
432
432
  *
433
- * resolveCapabilitiesNub({ type: 'theme.changed' })
433
+ * resolveCapabilitiesNap({ type: 'theme.changed' })
434
434
  * // => { senderCap: null, recipientCap: 'theme:read' }
435
435
  *
436
- * resolveCapabilitiesNub({ type: 'signer.signEvent' })
436
+ * resolveCapabilitiesNap({ type: 'signer.signEvent' })
437
437
  * // => { senderCap: null, recipientCap: null } // domain removed
438
438
  * ```
439
439
  */
440
- declare function resolveCapabilitiesNub(msg: NubMessage): CapabilityResolution;
440
+ declare function resolveCapabilitiesNap(msg: NapMessage): CapabilityResolution;
441
441
 
442
- export { type AclEntry, type AclState, CAP_ALL, CAP_CACHE_READ, CAP_CACHE_WRITE, CAP_HOTKEY_FORWARD, CAP_NONE, CAP_RELAY_READ, CAP_RELAY_WRITE, CAP_SIGN_EVENT, CAP_SIGN_NIP04, CAP_SIGN_NIP44, CAP_STATE_READ, CAP_STATE_WRITE, type CapabilityResolution, DEFAULT_QUOTA, type Identity, type NubMessage, block, check, createState, deserialize, getQuota, grant, migrateAclState, resolveCapabilitiesNub, revoke, serialize, setQuota, toKey, unblock };
442
+ export { type AclEntry, type AclState, CAP_ALL, CAP_CACHE_READ, CAP_CACHE_WRITE, CAP_HOTKEY_FORWARD, CAP_NONE, CAP_RELAY_READ, CAP_RELAY_WRITE, CAP_SIGN_EVENT, CAP_SIGN_NIP04, CAP_SIGN_NIP44, CAP_STATE_READ, CAP_STATE_WRITE, type CapabilityResolution, DEFAULT_QUOTA, type Identity, type NapMessage, block, check, createState, deserialize, getQuota, grant, migrateAclState, resolveCapabilitiesNap, revoke, serialize, setQuota, toKey, unblock };
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  CAP_NOTIFY_CHANNEL,
9
9
  CAP_NOTIFY_SEND,
10
10
  CAP_THEME_READ
11
- } from "./chunk-RNHAOBS6.js";
11
+ } from "./chunk-6GH7LZSY.js";
12
12
 
13
13
  // src/types.ts
14
14
  var CAP_RELAY_READ = 1 << 0;
@@ -204,7 +204,7 @@ function storageMap(action) {
204
204
  if (action === "set" || action === "remove") return { senderCap: "state:write", recipientCap: null };
205
205
  return { senderCap: null, recipientCap: null };
206
206
  }
207
- function ifcMap(action) {
207
+ function incMap(action) {
208
208
  if (action === "emit" || action === "channel.emit" || action === "channel.broadcast") {
209
209
  return { senderCap: "relay:write", recipientCap: "relay:read" };
210
210
  }
@@ -217,7 +217,7 @@ function configMap(action) {
217
217
  return { senderCap: "config:read", recipientCap: null };
218
218
  }
219
219
  function resourceMap(action) {
220
- if (action === "bytes.result" || action === "bytes.error") {
220
+ if (action === "bytes.result" || action === "bytes.error" || action === "bytesMany.result" || action === "bytesMany.error") {
221
221
  return { senderCap: null, recipientCap: "resource:fetch" };
222
222
  }
223
223
  return { senderCap: "resource:fetch", recipientCap: null };
@@ -252,7 +252,7 @@ function themeMap(action) {
252
252
  if (action === "changed") return { senderCap: null, recipientCap: "theme:read" };
253
253
  return { senderCap: "theme:read", recipientCap: null };
254
254
  }
255
- function resolveCapabilitiesNub(msg) {
255
+ function resolveCapabilitiesNap(msg) {
256
256
  const dotIdx = msg.type.indexOf(".");
257
257
  if (dotIdx === -1) return { senderCap: null, recipientCap: null };
258
258
  const domain = msg.type.slice(0, dotIdx);
@@ -270,10 +270,8 @@ function resolveCapabilitiesNub(msg) {
270
270
  return notifyMap(action);
271
271
  case "storage":
272
272
  return storageMap(action);
273
- case "ifc":
274
273
  case "inc":
275
- return ifcMap(action);
276
- // inc is the NAP rename of ifc (D5 / ALIGN-06)
274
+ return incMap(action);
277
275
  case "theme":
278
276
  return themeMap(action);
279
277
  case "config":
@@ -327,7 +325,7 @@ export {
327
325
  getQuota,
328
326
  grant,
329
327
  migrateAclState,
330
- resolveCapabilitiesNub,
328
+ resolveCapabilitiesNap,
331
329
  revoke,
332
330
  serialize,
333
331
  setQuota,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/check.ts","../src/mutations.ts","../src/migrate.ts","../src/resolve.ts"],"sourcesContent":["/**\n * @kehto/acl — Type definitions and capability bit constants.\n *\n * All types use Readonly<> to enforce immutability at the type level.\n * Capability constants are bitfield values for fast check/grant/revoke.\n */\n\n/** relay:read — subscribe to relay events */\nexport const CAP_RELAY_READ = 1 << 0; // 1\n/** relay:write — publish events to relays */\nexport const CAP_RELAY_WRITE = 1 << 1; // 2\n/** cache:read — read from local cache */\nexport const CAP_CACHE_READ = 1 << 2; // 4\n/** cache:write — write to local cache */\nexport const CAP_CACHE_WRITE = 1 << 3; // 8\n/** hotkey:forward — forward keyboard shortcuts to shell */\nexport const CAP_HOTKEY_FORWARD = 1 << 4; // 16\n/** sign:event — request event signing */\nexport const CAP_SIGN_EVENT = 1 << 5; // 32\n/** sign:nip04 — request NIP-04 encrypt/decrypt */\nexport const CAP_SIGN_NIP04 = 1 << 6; // 64\n/** sign:nip44 — request NIP-44 encrypt/decrypt */\nexport const CAP_SIGN_NIP44 = 1 << 7; // 128\n/** state:read — read napplet-scoped state */\nexport const CAP_STATE_READ = 1 << 8; // 256\n/** state:write — write napplet-scoped state */\nexport const CAP_STATE_WRITE = 1 << 9; // 512\n\n/** All capabilities granted (bits 0-9 set) */\nexport const CAP_ALL = (1 << 10) - 1; // 1023\n\n/** No capabilities granted */\nexport const CAP_NONE = 0;\n\n/**\n * Napplet identity — composite key for ACL lookups.\n *\n * Under NIP-5D v0.1.0, identity is assigned from the NIP-5A manifest\n * at iframe creation time. The pubkey field is no longer used.\n *\n * @param pubkey - (deprecated) Ephemeral AUTH keypair pubkey. Ignored by toKey().\n * @param dTag - Derived tag (deterministic from napp type)\n * @param hash - Aggregate hash of napplet build artifacts\n */\nexport interface Identity {\n /** @deprecated NIP-5D: AUTH keypair no longer exists. Pass '' or omit entirely.\n * Kept as optional for backward compatibility during data migration. */\n readonly pubkey?: string;\n readonly dTag: string;\n readonly hash: string;\n}\n\n/**\n * A single ACL entry for one napplet identity.\n *\n * @param caps - Bitfield of granted capabilities (use CAP_* constants)\n * @param blocked - Orthogonal block flag; when true, all checks fail regardless of caps\n * @param quota - State storage quota in bytes\n */\nexport interface AclEntry {\n readonly caps: number;\n readonly blocked: boolean;\n readonly quota: number;\n}\n\n/**\n * Complete ACL state — immutable data structure.\n *\n * All mutations return a new AclState; the original is never modified.\n *\n * @param defaultPolicy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities\n * @param entries - Map from composite key ('dTag:hash') to AclEntry\n */\nexport interface AclState {\n readonly defaultPolicy: 'permissive' | 'restrictive';\n readonly entries: Readonly<Record<string, AclEntry>>;\n}\n\n/** Default state storage quota in bytes (512 KB) */\nexport const DEFAULT_QUOTA = 512 * 1024;\n","/**\n * @kehto/acl — Pure check function.\n *\n * Determines whether an identity has a specific capability.\n * No side effects, no I/O, no mutations.\n */\n\nimport type { AclState, Identity } from './types.js';\n\n/**\n * Compute composite key from identity fields.\n *\n * Under NIP-5D v0.1.0, the key is 'dTag:hash' (pubkey is ignored).\n *\n * @param identity - Napplet identity\n * @returns Composite key string 'dTag:hash'\n *\n * @example\n * ```ts\n * toKey({ dTag: 'chat', hash: 'ff00' })\n * // => 'chat:ff00'\n * ```\n */\nexport function toKey(identity: Identity): string {\n return `${identity.dTag}:${identity.hash}`;\n}\n\n/**\n * Check whether an identity has a specific capability.\n *\n * Decision logic:\n * 1. If identity has no entry: return based on defaultPolicy\n * - 'permissive' → true (all caps granted to unknown identities)\n * - 'restrictive' → false (all caps denied to unknown identities)\n * 2. If identity is blocked: return false (blocked overrides all caps)\n * 3. Otherwise: return (entry.caps & cap) !== 0\n *\n * @param state - Current ACL state (immutable)\n * @param identity - Napplet identity to check\n * @param cap - Capability bit constant (e.g., CAP_RELAY_READ)\n * @returns true if the identity has the capability, false otherwise\n *\n * @example\n * ```ts\n * import { check, createState, grant } from '@kehto/acl';\n * import { CAP_RELAY_READ } from '@kehto/acl';\n *\n * const state = createState('restrictive');\n * const id = { dTag: 'chat', hash: 'ff00' };\n *\n * check(state, id, CAP_RELAY_READ); // false (restrictive, no entry)\n *\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function check(state: AclState, identity: Identity, cap: number): boolean {\n const key = toKey(identity);\n const entry = state.entries[key];\n if (!entry) {\n return state.defaultPolicy === 'permissive';\n }\n if (entry.blocked) {\n return false;\n }\n return (entry.caps & cap) !== 0;\n}\n","/**\n * @kehto/acl — Pure state mutation functions.\n *\n * Every function takes an AclState and returns a NEW AclState.\n * The original state is never modified. No side effects, no I/O.\n */\n\nimport { CAP_ALL, DEFAULT_QUOTA, type AclEntry, type AclState, type Identity } from './types.js';\nimport { toKey } from './check.js';\n\n/**\n * Create a new ACL state with the given default policy.\n *\n * @param policy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities.\n * Defaults to 'permissive'.\n * @returns A new empty AclState\n *\n * @example\n * ```ts\n * const state = createState('restrictive');\n * // { defaultPolicy: 'restrictive', entries: {} }\n * ```\n */\nexport function createState(policy: 'permissive' | 'restrictive' = 'permissive'): AclState {\n return { defaultPolicy: policy, entries: {} };\n}\n\n/**\n * Get the entry for an identity, or a default entry based on policy.\n * Internal helper — not exported.\n */\nfunction getEntry(state: AclState, key: string): AclEntry {\n const existing = state.entries[key];\n if (existing) return existing;\n // Default entry: all caps if permissive, no caps if restrictive\n return {\n caps: state.defaultPolicy === 'permissive' ? CAP_ALL : 0,\n blocked: false,\n quota: DEFAULT_QUOTA,\n };\n}\n\n/**\n * Grant a capability to an identity.\n *\n * If the identity has no entry, one is created with default caps plus the granted cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to grant (e.g., CAP_RELAY_READ)\n * @returns New AclState with the capability granted\n *\n * @example\n * ```ts\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function grant(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps | cap },\n },\n };\n}\n\n/**\n * Revoke a capability from an identity.\n *\n * If the identity has no entry, one is created with default caps minus the revoked cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to revoke (e.g., CAP_RELAY_WRITE)\n * @returns New AclState with the capability revoked\n *\n * @example\n * ```ts\n * const state2 = revoke(state, id, CAP_RELAY_WRITE);\n * check(state2, id, CAP_RELAY_WRITE); // false\n * ```\n */\nexport function revoke(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps & ~cap },\n },\n };\n}\n\n/**\n * Block an identity.\n *\n * A blocked identity fails all capability checks regardless of granted caps.\n * The caps bitfield is preserved — unblocking restores previous capabilities.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to block\n * @returns New AclState with the identity blocked\n *\n * @example\n * ```ts\n * const state2 = block(state, id);\n * check(state2, id, CAP_RELAY_READ); // false (blocked)\n * ```\n */\nexport function block(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: true },\n },\n };\n}\n\n/**\n * Unblock an identity.\n *\n * Restores capability checks to use the caps bitfield.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to unblock\n * @returns New AclState with the identity unblocked\n *\n * @example\n * ```ts\n * const state2 = unblock(state, id);\n * check(state2, id, CAP_RELAY_READ); // true (if cap was granted)\n * ```\n */\nexport function unblock(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: false },\n },\n };\n}\n\n/**\n * Set the state storage quota for an identity.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param bytes - Quota in bytes\n * @returns New AclState with the quota set\n *\n * @example\n * ```ts\n * const state2 = setQuota(state, id, 1024 * 1024); // 1 MB\n * getQuota(state2, id); // 1048576\n * ```\n */\nexport function setQuota(state: AclState, identity: Identity, bytes: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, quota: bytes },\n },\n };\n}\n\n/**\n * Get the state storage quota for an identity.\n *\n * Returns DEFAULT_QUOTA (512 KB) if no entry exists.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @returns Quota in bytes\n *\n * @example\n * ```ts\n * getQuota(state, id); // 524288 (default 512 KB)\n * ```\n */\nexport function getQuota(state: AclState, identity: Identity): number {\n const key = toKey(identity);\n const entry = state.entries[key];\n return entry?.quota ?? DEFAULT_QUOTA;\n}\n\n/**\n * Serialize ACL state to a JSON string.\n *\n * Pure function — no I/O. The persistence adapter in @kehto/shell\n * uses this to write state to localStorage or other backends.\n *\n * @param state - ACL state to serialize\n * @returns JSON string representation\n *\n * @example\n * ```ts\n * const json = serialize(state);\n * localStorage.setItem('napplet:acl', json);\n * ```\n */\nexport function serialize(state: AclState): string {\n return JSON.stringify(state);\n}\n\n/**\n * Deserialize ACL state from a JSON string.\n *\n * Pure function — no I/O. Returns a valid AclState or a fresh\n * permissive state if the input is invalid.\n *\n * @param json - JSON string to parse\n * @returns Parsed AclState, or fresh permissive state on parse failure\n *\n * @example\n * ```ts\n * const json = localStorage.getItem('napplet:acl') ?? '';\n * const state = deserialize(json);\n * ```\n */\nexport function deserialize(json: string): AclState {\n try {\n const parsed = JSON.parse(json);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n (parsed.defaultPolicy === 'permissive' || parsed.defaultPolicy === 'restrictive') &&\n typeof parsed.entries === 'object' &&\n parsed.entries !== null\n ) {\n const entries: Record<string, AclEntry> = {};\n for (const [key, value] of Object.entries(parsed.entries)) {\n const entry = value as Record<string, unknown>;\n if (\n typeof entry.caps === 'number' &&\n typeof entry.blocked === 'boolean' &&\n typeof entry.quota === 'number'\n ) {\n entries[key] = {\n caps: entry.caps,\n blocked: entry.blocked,\n quota: entry.quota,\n };\n }\n }\n return { defaultPolicy: parsed.defaultPolicy, entries };\n }\n } catch {\n // Invalid JSON — fall through to default\n }\n return createState('permissive');\n}\n","/**\n * @kehto/acl — ACL state migration utility.\n *\n * Provides a pure function to migrate persisted ACL state from the old\n * 3-segment composite key format (pubkey:dTag:hash) to the new 2-segment\n * format (dTag:hash) introduced in NIP-5D v0.1.0.\n *\n * No I/O, no side effects. Pure function: takes AclState, returns AclState.\n */\n\nimport type { AclState, AclEntry } from './types.js';\n\n/**\n * Migrate ACL state from old 3-segment key format to new 2-segment key format.\n *\n * Converts entries stored under 'pubkey:dTag:hash' keys to 'dTag:hash' keys.\n * If two old entries map to the same dTag:hash, merges them conservatively:\n * - caps: OR of both bitfields (never removes a granted capability)\n * - blocked: OR of both flags (blocks if either source was blocked)\n * - quota: MAX of both values (keeps the higher allocation)\n *\n * Idempotent: if no 3-segment keys are found, returns the original state\n * unchanged (same object reference).\n *\n * @param state - Current ACL state (may contain old-format entries)\n * @returns Migrated AclState with only 2-segment keys, or the original\n * state unchanged if no migration was needed\n *\n * @example\n * ```ts\n * const oldState = deserialize(localStorage.getItem('napplet:acl') ?? '');\n * const newState = migrateAclState(oldState);\n * if (newState !== oldState) {\n * // Migration occurred — persist the new format\n * localStorage.setItem('napplet:acl', serialize(newState));\n * }\n * ```\n */\nexport function migrateAclState(state: AclState): AclState {\n const newEntries: Record<string, AclEntry> = {};\n let migrated = false;\n\n for (const [key, entry] of Object.entries(state.entries)) {\n const parts = key.split(':');\n if (parts.length === 3) {\n // Old format: pubkey:dTag:hash -> dTag:hash\n const newKey = `${parts[1]}:${parts[2]}`;\n const existing = newEntries[newKey];\n if (existing) {\n // Merge: union caps, preserve block, max quota\n newEntries[newKey] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[newKey] = entry;\n }\n migrated = true;\n } else {\n // Already new format or other key — merge if collision with a previously migrated entry\n const existing = newEntries[key];\n if (existing) {\n // Collision: old-format entry was processed first under the same key — merge\n newEntries[key] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[key] = entry;\n }\n }\n }\n\n if (!migrated) return state; // No old entries found — return original unchanged\n\n return { defaultPolicy: state.defaultPolicy, entries: newEntries };\n}\n","/**\n * @kehto/acl — NUB/NAP domain capability resolution (8 canonical + config,\n * resource, cvm, outbox, upload, intent).\n *\n * Maps NUB message types (e.g., 'relay.subscribe', 'identity.getProfile') to\n * the capability strings required by sender and recipient. This is the\n * canonical source for \"which capability does this NUB operation require?\"\n * in the @kehto/acl package.\n *\n * Canonical NIP-5D 8 domains: identity, keys, media, notify, relay,\n * storage, ifc, theme. Extended in v1.7 with: config (Phase 39, 9th domain),\n * resource (Phase 40, 10th domain). The v1.1 `signer` domain is REMOVED —\n * getPublicKey/getRelays migrated to `identity`; signEvent/nip04/nip44 have\n * no napplet-visible surface (shell handles encryption inside\n * `relay.publishEncrypted`).\n *\n * Zero dependencies. No imports from @napplet/core or any external package.\n *\n * @see packages/acl/src/capabilities.ts for cap string constants + ALL_CAPABILITIES.\n * @see docs/ACL-MIGRATION.md section 2 — Capability Constant to NUB Domain Mapping.\n */\n\n/**\n * Minimal message shape used for capability resolution.\n *\n * Compatible with NappletMessage from @napplet/core, but defined here\n * independently to maintain @kehto/acl's zero-dependency constraint.\n *\n * @param type - NUB message type, e.g. 'relay.subscribe', 'identity.getProfile'\n */\nexport interface NubMessage {\n readonly type: string;\n}\n\n/**\n * Result of resolving what capabilities a NUB message requires.\n *\n * | Field | Description |\n * |----------------|----------------------------------------------------------------|\n * | `senderCap` | Capability the sender must have, or null if no check needed |\n * | `recipientCap` | Capability the recipient must have, or null if no check needed |\n *\n * @param senderCap - Capability the sender must have, or null if no ACL gate required\n * @param recipientCap - Capability the recipient must have, or null if no recipient check\n */\nexport interface CapabilityResolution {\n readonly senderCap: string | null;\n readonly recipientCap: string | null;\n}\n\n/**\n * `relay.*` — split publish / publishEncrypted / read actions.\n *\n * - `publish` → sender `relay:write`, recipient `relay:read`.\n * - `publishEncrypted` → sender `relay:write`, recipient `null` (the shell\n * handles encryption internally; no napplet-visible recipient ACL check).\n * - `subscribe` / `query` / `close` (and `.result` / `event` / `eose` /\n * `closed` / `publish.result` / `publishEncrypted.result`) → sender\n * `relay:read`, recipient `null`.\n */\nfunction relayMap(action: string): CapabilityResolution {\n if (action === 'publish') return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n if (action === 'publishEncrypted') return { senderCap: 'relay:write', recipientCap: null };\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `identity.*` — split shell-public reads from gated profile reads.\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets cannot sign,\n * encrypt, or decrypt (encryption is delegated via `relay.publishEncrypted`).\n *\n * - `getPublicKey` / `getRelays` → `null`/`null` (shell-public info).\n * - `getProfile` / `getFollows` / `getList` / `getZaps` / `getMutes` /\n * `getBlocked` / `getBadges` (and any other identity read) → sender\n * `identity:read`, recipient `null`.\n */\nfunction identityMap(action: string): CapabilityResolution {\n if (action === 'getPublicKey' || action === 'getRelays') {\n return { senderCap: null, recipientCap: null };\n }\n return { senderCap: 'identity:read', recipientCap: null };\n}\n\n/**\n * `keys.*` — split forwarding from binding lifecycle.\n *\n * - `forward` / `action` → `keys:forward`.\n * - `registerAction` / `unregisterAction` / `bindings` → `keys:bind`.\n */\nfunction keysMap(action: string): CapabilityResolution {\n if (action === 'forward' || action === 'action') {\n return { senderCap: 'keys:forward', recipientCap: null };\n }\n return { senderCap: 'keys:bind', recipientCap: null };\n}\n\n/**\n * `notify.*` — split channel/permission registration from send/interaction.\n *\n * - `channel.register` / `permission.request` / `permission.result` → `notify:channel`.\n * - `send` / `dismiss` / `badge` / `send.result` / `action` / `clicked` /\n * `dismissed` / `controls` (and any other notify action) → `notify:send`.\n */\nfunction notifyMap(action: string): CapabilityResolution {\n if (\n action === 'channel.register' ||\n action === 'permission.request' ||\n action === 'permission.result'\n ) {\n return { senderCap: 'notify:channel', recipientCap: null };\n }\n return { senderCap: 'notify:send', recipientCap: null };\n}\n\n/**\n * `storage.*` — narrowed to the canonical 4 actions (get/keys/set/remove).\n *\n * - `get` / `keys` → `state:read`.\n * - `set` / `remove` → `state:write`.\n * - anything else (incl. the removed `clear`) → `null`/`null`. The runtime\n * storage handler rejects non-canonical actions before ACL resolution so\n * napplets see the explicit rejection rather than a misleading cap denial.\n */\nfunction storageMap(action: string): CapabilityResolution {\n if (action === 'get' || action === 'keys') return { senderCap: 'state:read', recipientCap: null };\n if (action === 'set' || action === 'remove') return { senderCap: 'state:write', recipientCap: null };\n return { senderCap: null, recipientCap: null };\n}\n\n/**\n * `ifc.*` / `inc.*` — topic + channel sub-protocol.\n *\n * `inc` is the NAP rename of `ifc` (adopted in `@napplet/* >=0.9.0`). Both\n * domains share exactly the same capability mapping via `ifcMap` so they\n * cannot drift apart (D5 / ALIGN-06).\n *\n * - Write actions (`emit`, `channel.emit`, `channel.broadcast`) → sender\n * `relay:write`, recipient `relay:read`. Semantically equivalent to relay\n * publish: point-to-point or fan-out writes gate on relay-write at wire\n * level even though channel membership ACL is enforced at `channel.open`.\n * - Read / control actions (`subscribe`, `unsubscribe`, `channel.open`,\n * `channel.list`, `channel.close`) → sender\n * `relay:read`, recipient `null`. Channel open-time ACL semantics: the\n * caller must already hold `relay:read`, and channel membership is\n * recorded by the ifc handler.\n */\nfunction ifcMap(action: string): CapabilityResolution {\n if (action === 'emit' || action === 'channel.emit' || action === 'channel.broadcast') {\n return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n }\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `config.*` — NUB-CONFIG reference service (v1.7 Phase 39 / 9th NUB domain).\n *\n * Asymmetric protocol: napplet reads, shell writes. ALL napplet-originated\n * config messages require `config:read`. Shell→napplet pushes\n * (`config.values`, `config.registerSchema.result`, `config.schemaError`)\n * are gated by the recipient's `config:read` cap.\n *\n * Anti-overlap: NUB-STORAGE remains the general key-value surface\n * (`state:read`/`state:write`). NUB-CONFIG is shell-managed per-napplet\n * configuration only — see CONFIG-04 scope boundary docs.\n */\nfunction configMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold config:read to see them).\n if (action === 'values' || action === 'registerSchema.result' || action === 'schemaError') {\n return { senderCap: null, recipientCap: 'config:read' };\n }\n // Napplet-originated requests: sender gate.\n return { senderCap: 'config:read', recipientCap: null };\n}\n\n/**\n * `resource.*` — NUB-RESOURCE authenticated fetch proxy (v1.7 Phase 40 / 10th NUB domain).\n *\n * Asymmetric protocol: napplet initiates fetch requests, shell proxies and responds.\n *\n * - `bytes` / `cancel` (napplet → shell requests) →\n * sender `resource:fetch`, recipient `null`. The napplet must hold\n * `resource:fetch` to issue a bytes request or cancel one.\n * - `bytes.result` / `bytes.error` (shell → napplet pushes) →\n * sender `null`, recipient `resource:fetch`. The napplet must hold\n * `resource:fetch` to receive the result/error push.\n * - Unknown resource.* actions → sender `resource:fetch`, recipient `null`\n * (default sender gate: napplet must hold resource:fetch to send anything\n * in the resource domain).\n */\nfunction resourceMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold resource:fetch to see them).\n if (action === 'bytes.result' || action === 'bytes.error') {\n return { senderCap: null, recipientCap: 'resource:fetch' };\n }\n // Napplet-originated requests: sender gate (bytes, cancel, and any unknown).\n return { senderCap: 'resource:fetch', recipientCap: null };\n}\n\n/**\n * `cvm.*` — NAP-CVM ContextVM bridge. Single `cvm:call` cap gates the domain.\n *\n * - `discover` / `request` / `close` (napplet → shell requests) →\n * sender `cvm:call`, recipient `null`. The napplet must hold `cvm:call`\n * to query servers or send MCP messages.\n * - `discover.result` / `request.result` / `close.result` / `event`\n * (shell → napplet pushes) → sender `null`, recipient `cvm:call`. The push\n * is gated against the receiving napplet's cap so a napplet without\n * `cvm:call` never sees CVM results or server-pushed events.\n * - Unknown `cvm.*` actions → sender `cvm:call` (default sender gate).\n */\nfunction cvmMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (action === 'event' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'cvm:call' };\n }\n // Napplet-originated requests: sender gate (discover, request, close, unknown).\n return { senderCap: 'cvm:call', recipientCap: null };\n}\n\n/**\n * `outbox.*` — NAP-OUTBOX outbox-aware relay routing (12th NAP domain).\n *\n * Split read/write like the `relay` domain, but with dedicated caps so a shell\n * can grant outbox routing independently of raw relay access:\n *\n * - `publish` (napplet → shell) → sender `outbox:write`,\n * recipient `null`. The shell signs and fans the event out to the relevant\n * write relays; there is no napplet-visible recipient ACL check.\n * - `query` / `subscribe` / `close` / `resolveRelays` (and any other\n * napplet-originated request) → sender `outbox:read`,\n * recipient `null`.\n * - `event` / `eose` / `closed` / `*.result` / `*.error` (shell → napplet\n * pushes) → sender `null`,\n * recipient `outbox:read`. The push is gated against the receiving napplet's\n * cap so a napplet without `outbox:read` never sees results or streamed\n * events.\n */\nfunction outboxMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold outbox:read to see them).\n if (\n action === 'event' ||\n action === 'eose' ||\n action === 'closed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'outbox:read' };\n }\n // Publish is the write op.\n if (action === 'publish') return { senderCap: 'outbox:write', recipientCap: null };\n // Napplet-originated reads: query, subscribe, close, resolveRelays, unknown.\n return { senderCap: 'outbox:read', recipientCap: null };\n}\n\n/**\n * `upload.*` — NAP-UPLOAD shell-mediated file/blob upload (13th NAP domain).\n *\n * A single `upload:write` cap gates the domain — uploading is the sensitive op\n * (network egress + identity-linking), and `status` only inspects the requesting\n * napplet's own uploads, so it rides the same grant rather than a separate read\n * cap.\n *\n * - `upload` / `status` (and any other napplet-originated request) → sender\n * `upload:write`, recipient `null`.\n * - `upload.result` / `status.result` / `status.changed` / `*.error` (shell →\n * napplet pushes) → sender `null`, recipient `upload:write`. The push is gated\n * against the receiving napplet's cap so a napplet without `upload:write`\n * never sees results or progress updates.\n */\nfunction uploadMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (\n action === 'status.changed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'upload:write' };\n }\n // Napplet-originated requests: sender gate (upload, status, unknown).\n return { senderCap: 'upload:write', recipientCap: null };\n}\n\n/**\n * `intent.*` — NAP-INTENT archetype intent dispatch (14th NAP domain).\n *\n * Split read/write so a read-only class can introspect available handlers but\n * cannot dispatch (mirrors `outbox` / `relay`, where the write op is the\n * sensitive one — here `invoke` is a focus-stealing cross-napplet navigation):\n *\n * - `invoke` (napplet → shell) → sender `intent:write`,\n * recipient `null`. The shell resolves the archetype to a handler, creates or\n * focuses its window, and delivers the payload.\n * - `available` / `handlers` (and any other napplet-originated request) →\n * sender `intent:read`, recipient `null`. Read-side catalog introspection.\n * - `changed` / `*.result` / `*.error` (shell → napplet pushes) →\n * sender `null`, recipient `intent:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `intent:read` never sees\n * availability updates or invoke results.\n */\nfunction intentMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold intent:read to see them).\n if (action === 'changed' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'intent:read' };\n }\n // Invoke is the write op (cross-napplet dispatch / focus-steal).\n if (action === 'invoke') return { senderCap: 'intent:write', recipientCap: null };\n // Napplet-originated reads: available, handlers, unknown.\n return { senderCap: 'intent:read', recipientCap: null };\n}\n\n/**\n * `theme.*` — napplet read gate vs shell-initiated push.\n *\n * - `get` / `get.result` (and any other napplet-originated query) →\n * sender `theme:read`, recipient `null`.\n * - `changed` (shell → napplet push) →\n * sender `null`, recipient `theme:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `theme:read` never sees\n * the update.\n *\n * Note: theme's runtime/service wiring lands in Phase 13. The ACL gate is\n * defined here in Phase 12 so the cap surface is canonical ahead of the\n * runtime work.\n */\nfunction themeMap(action: string): CapabilityResolution {\n if (action === 'changed') return { senderCap: null, recipientCap: 'theme:read' };\n return { senderCap: 'theme:read', recipientCap: null };\n}\n\n/**\n * Resolve the capabilities required by a NUB message.\n *\n * Splits `msg.type` on '.' to obtain `[domain, action]`, then dispatches to\n * a per-domain mapper. Unknown domains return `null/null` (silently ignored).\n *\n * **NUB domain mapping table (8 canonical domains):**\n *\n * | Domain | Action(s) | senderCap | recipientCap |\n * |------------|--------------------------------------------------------------|-----------------|---------------|\n * | `relay` | `subscribe`, `query`, `close`, results/pushes | `relay:read` | `null` |\n * | `relay` | `publish` | `relay:write` | `relay:read` |\n * | `relay` | `publishEncrypted` | `relay:write` | `null` |\n * | `identity` | `getPublicKey`, `getRelays` | `null` | `null` |\n * | `identity` | `getProfile/getFollows/getList/getZaps/getMutes/...` | `identity:read` | `null` |\n * | `keys` | `forward`, `action` | `keys:forward` | `null` |\n * | `keys` | `registerAction`, `unregisterAction`, `bindings` | `keys:bind` | `null` |\n * | `media` | any | `media:control` | `null` |\n * | `notify` | `channel.register`, `permission.request`, `permission.result` | `notify:channel`| `null` |\n * | `notify` | `send`, `dismiss`, `badge`, `clicked`, `action`, ... | `notify:send` | `null` |\n * | `storage` | `get`, `keys` | `state:read` | `null` |\n * | `storage` | `set`, `remove` | `state:write` | `null` |\n * | `storage` | any other (incl. removed `clear`) | `null` | `null` |\n * | `ifc`/`inc` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |\n * | `ifc`/`inc` | `subscribe`, `unsubscribe`, `channel.open/list/close` | `relay:read` | `null` |\n * | `theme` | `get`, `get.result` | `theme:read` | `null` |\n * | `theme` | `changed` (shell → napplet push) | `null` | `theme:read` |\n * | `config` | `get`, `subscribe`, `unsubscribe`, `registerSchema`, `openSettings` | `config:read` | `null` |\n * | `config` | `values`, `registerSchema.result`, `schemaError` (shell → napplet pushes) | `null` | `config:read` |\n * | `resource` | `bytes`, `cancel` (napplet → shell requests) | `resource:fetch`| `null` |\n * | `resource` | `bytes.result`, `bytes.error` (shell → napplet pushes) | `null` | `resource:fetch` |\n * | `intent` | `invoke` (napplet → shell) | `intent:write` | `null` |\n * | `intent` | `available`, `handlers` (napplet → shell) | `intent:read` | `null` |\n * | `intent` | `changed`, `*.result`, `*.error` (shell → napplet pushes) | `null` | `intent:read` |\n * | unknown | any | `null` | `null` |\n *\n * The `signer` domain is REMOVED — signer messages fall through to the\n * default null/null branch. `getPublicKey`/`getRelays` migrated to\n * `identity`; napplet-visible signing does not exist in NIP-5D (shell\n * signs internally for `relay.publishEncrypted`).\n *\n * @param msg - Message with a `type` field in NUB format (e.g., 'relay.subscribe')\n * @returns CapabilityResolution with senderCap and recipientCap (each may be null)\n *\n * @example\n * ```ts\n * resolveCapabilitiesNub({ type: 'relay.subscribe' })\n * // => { senderCap: 'relay:read', recipientCap: null }\n *\n * resolveCapabilitiesNub({ type: 'relay.publishEncrypted' })\n * // => { senderCap: 'relay:write', recipientCap: null }\n *\n * resolveCapabilitiesNub({ type: 'identity.getProfile' })\n * // => { senderCap: 'identity:read', recipientCap: null }\n *\n * resolveCapabilitiesNub({ type: 'keys.forward' })\n * // => { senderCap: 'keys:forward', recipientCap: null }\n *\n * resolveCapabilitiesNub({ type: 'ifc.channel.broadcast' })\n * // => { senderCap: 'relay:write', recipientCap: 'relay:read' }\n *\n * resolveCapabilitiesNub({ type: 'theme.changed' })\n * // => { senderCap: null, recipientCap: 'theme:read' }\n *\n * resolveCapabilitiesNub({ type: 'signer.signEvent' })\n * // => { senderCap: null, recipientCap: null } // domain removed\n * ```\n */\nexport function resolveCapabilitiesNub(msg: NubMessage): CapabilityResolution {\n const dotIdx = msg.type.indexOf('.');\n if (dotIdx === -1) return { senderCap: null, recipientCap: null };\n const domain = msg.type.slice(0, dotIdx);\n const action = msg.type.slice(dotIdx + 1);\n\n switch (domain) {\n case 'relay': return relayMap(action);\n case 'identity': return identityMap(action);\n case 'keys': return keysMap(action);\n case 'media': return { senderCap: 'media:control', recipientCap: null };\n case 'notify': return notifyMap(action);\n case 'storage': return storageMap(action);\n case 'ifc':\n case 'inc': return ifcMap(action); // inc is the NAP rename of ifc (D5 / ALIGN-06)\n case 'theme': return themeMap(action);\n case 'config': return configMap(action);\n case 'resource': return resourceMap(action); // Phase 40 (RESOURCE-02)\n case 'cvm': return cvmMap(action); // NAP-CVM ContextVM bridge\n case 'outbox': return outboxMap(action); // NAP-OUTBOX outbox-aware relay routing\n case 'upload': return uploadMap(action); // NAP-UPLOAD shell-mediated file/blob upload\n case 'intent': return intentMap(action); // NAP-INTENT archetype intent dispatch\n default: return { senderCap: null, recipientCap: null };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/types.ts","../src/check.ts","../src/mutations.ts","../src/migrate.ts","../src/resolve.ts"],"sourcesContent":["/**\n * @kehto/acl — Type definitions and capability bit constants.\n *\n * All types use Readonly<> to enforce immutability at the type level.\n * Capability constants are bitfield values for fast check/grant/revoke.\n */\n\n/** relay:read — subscribe to relay events */\nexport const CAP_RELAY_READ = 1 << 0; // 1\n/** relay:write — publish events to relays */\nexport const CAP_RELAY_WRITE = 1 << 1; // 2\n/** cache:read — read from local cache */\nexport const CAP_CACHE_READ = 1 << 2; // 4\n/** cache:write — write to local cache */\nexport const CAP_CACHE_WRITE = 1 << 3; // 8\n/** hotkey:forward — forward keyboard shortcuts to shell */\nexport const CAP_HOTKEY_FORWARD = 1 << 4; // 16\n/** sign:event — request event signing */\nexport const CAP_SIGN_EVENT = 1 << 5; // 32\n/** sign:nip04 — request NIP-04 encrypt/decrypt */\nexport const CAP_SIGN_NIP04 = 1 << 6; // 64\n/** sign:nip44 — request NIP-44 encrypt/decrypt */\nexport const CAP_SIGN_NIP44 = 1 << 7; // 128\n/** state:read — read napplet-scoped state */\nexport const CAP_STATE_READ = 1 << 8; // 256\n/** state:write — write napplet-scoped state */\nexport const CAP_STATE_WRITE = 1 << 9; // 512\n\n/** All capabilities granted (bits 0-9 set) */\nexport const CAP_ALL = (1 << 10) - 1; // 1023\n\n/** No capabilities granted */\nexport const CAP_NONE = 0;\n\n/**\n * Napplet identity — composite key for ACL lookups.\n *\n * Under NIP-5D v0.1.0, identity is assigned from the NIP-5A manifest\n * at iframe creation time. The pubkey field is no longer used.\n *\n * @param pubkey - (deprecated) Ephemeral AUTH keypair pubkey. Ignored by toKey().\n * @param dTag - Derived tag (deterministic from napp type)\n * @param hash - Aggregate hash of napplet build artifacts\n */\nexport interface Identity {\n /** @deprecated NIP-5D: AUTH keypair no longer exists. Pass '' or omit entirely.\n * Kept as optional for backward compatibility during data migration. */\n readonly pubkey?: string;\n readonly dTag: string;\n readonly hash: string;\n}\n\n/**\n * A single ACL entry for one napplet identity.\n *\n * @param caps - Bitfield of granted capabilities (use CAP_* constants)\n * @param blocked - Orthogonal block flag; when true, all checks fail regardless of caps\n * @param quota - State storage quota in bytes\n */\nexport interface AclEntry {\n readonly caps: number;\n readonly blocked: boolean;\n readonly quota: number;\n}\n\n/**\n * Complete ACL state — immutable data structure.\n *\n * All mutations return a new AclState; the original is never modified.\n *\n * @param defaultPolicy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities\n * @param entries - Map from composite key ('dTag:hash') to AclEntry\n */\nexport interface AclState {\n readonly defaultPolicy: 'permissive' | 'restrictive';\n readonly entries: Readonly<Record<string, AclEntry>>;\n}\n\n/** Default state storage quota in bytes (512 KB) */\nexport const DEFAULT_QUOTA = 512 * 1024;\n","/**\n * @kehto/acl — Pure check function.\n *\n * Determines whether an identity has a specific capability.\n * No side effects, no I/O, no mutations.\n */\n\nimport type { AclState, Identity } from './types.js';\n\n/**\n * Compute composite key from identity fields.\n *\n * Under NIP-5D v0.1.0, the key is 'dTag:hash' (pubkey is ignored).\n *\n * @param identity - Napplet identity\n * @returns Composite key string 'dTag:hash'\n *\n * @example\n * ```ts\n * toKey({ dTag: 'chat', hash: 'ff00' })\n * // => 'chat:ff00'\n * ```\n */\nexport function toKey(identity: Identity): string {\n return `${identity.dTag}:${identity.hash}`;\n}\n\n/**\n * Check whether an identity has a specific capability.\n *\n * Decision logic:\n * 1. If identity has no entry: return based on defaultPolicy\n * - 'permissive' → true (all caps granted to unknown identities)\n * - 'restrictive' → false (all caps denied to unknown identities)\n * 2. If identity is blocked: return false (blocked overrides all caps)\n * 3. Otherwise: return (entry.caps & cap) !== 0\n *\n * @param state - Current ACL state (immutable)\n * @param identity - Napplet identity to check\n * @param cap - Capability bit constant (e.g., CAP_RELAY_READ)\n * @returns true if the identity has the capability, false otherwise\n *\n * @example\n * ```ts\n * import { check, createState, grant } from '@kehto/acl';\n * import { CAP_RELAY_READ } from '@kehto/acl';\n *\n * const state = createState('restrictive');\n * const id = { dTag: 'chat', hash: 'ff00' };\n *\n * check(state, id, CAP_RELAY_READ); // false (restrictive, no entry)\n *\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function check(state: AclState, identity: Identity, cap: number): boolean {\n const key = toKey(identity);\n const entry = state.entries[key];\n if (!entry) {\n return state.defaultPolicy === 'permissive';\n }\n if (entry.blocked) {\n return false;\n }\n return (entry.caps & cap) !== 0;\n}\n","/**\n * @kehto/acl — Pure state mutation functions.\n *\n * Every function takes an AclState and returns a NEW AclState.\n * The original state is never modified. No side effects, no I/O.\n */\n\nimport { CAP_ALL, DEFAULT_QUOTA, type AclEntry, type AclState, type Identity } from './types.js';\nimport { toKey } from './check.js';\n\n/**\n * Create a new ACL state with the given default policy.\n *\n * @param policy - 'permissive' grants all caps to unknown identities;\n * 'restrictive' denies all caps to unknown identities.\n * Defaults to 'permissive'.\n * @returns A new empty AclState\n *\n * @example\n * ```ts\n * const state = createState('restrictive');\n * // { defaultPolicy: 'restrictive', entries: {} }\n * ```\n */\nexport function createState(policy: 'permissive' | 'restrictive' = 'permissive'): AclState {\n return { defaultPolicy: policy, entries: {} };\n}\n\n/**\n * Get the entry for an identity, or a default entry based on policy.\n * Internal helper — not exported.\n */\nfunction getEntry(state: AclState, key: string): AclEntry {\n const existing = state.entries[key];\n if (existing) return existing;\n // Default entry: all caps if permissive, no caps if restrictive\n return {\n caps: state.defaultPolicy === 'permissive' ? CAP_ALL : 0,\n blocked: false,\n quota: DEFAULT_QUOTA,\n };\n}\n\n/**\n * Grant a capability to an identity.\n *\n * If the identity has no entry, one is created with default caps plus the granted cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to grant (e.g., CAP_RELAY_READ)\n * @returns New AclState with the capability granted\n *\n * @example\n * ```ts\n * const state2 = grant(state, id, CAP_RELAY_READ);\n * check(state2, id, CAP_RELAY_READ); // true\n * ```\n */\nexport function grant(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps | cap },\n },\n };\n}\n\n/**\n * Revoke a capability from an identity.\n *\n * If the identity has no entry, one is created with default caps minus the revoked cap.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param cap - Capability bit constant to revoke (e.g., CAP_RELAY_WRITE)\n * @returns New AclState with the capability revoked\n *\n * @example\n * ```ts\n * const state2 = revoke(state, id, CAP_RELAY_WRITE);\n * check(state2, id, CAP_RELAY_WRITE); // false\n * ```\n */\nexport function revoke(state: AclState, identity: Identity, cap: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, caps: entry.caps & ~cap },\n },\n };\n}\n\n/**\n * Block an identity.\n *\n * A blocked identity fails all capability checks regardless of granted caps.\n * The caps bitfield is preserved — unblocking restores previous capabilities.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to block\n * @returns New AclState with the identity blocked\n *\n * @example\n * ```ts\n * const state2 = block(state, id);\n * check(state2, id, CAP_RELAY_READ); // false (blocked)\n * ```\n */\nexport function block(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: true },\n },\n };\n}\n\n/**\n * Unblock an identity.\n *\n * Restores capability checks to use the caps bitfield.\n * Returns a new AclState — the original is not modified.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity to unblock\n * @returns New AclState with the identity unblocked\n *\n * @example\n * ```ts\n * const state2 = unblock(state, id);\n * check(state2, id, CAP_RELAY_READ); // true (if cap was granted)\n * ```\n */\nexport function unblock(state: AclState, identity: Identity): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, blocked: false },\n },\n };\n}\n\n/**\n * Set the state storage quota for an identity.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @param bytes - Quota in bytes\n * @returns New AclState with the quota set\n *\n * @example\n * ```ts\n * const state2 = setQuota(state, id, 1024 * 1024); // 1 MB\n * getQuota(state2, id); // 1048576\n * ```\n */\nexport function setQuota(state: AclState, identity: Identity, bytes: number): AclState {\n const key = toKey(identity);\n const entry = getEntry(state, key);\n return {\n ...state,\n entries: {\n ...state.entries,\n [key]: { ...entry, quota: bytes },\n },\n };\n}\n\n/**\n * Get the state storage quota for an identity.\n *\n * Returns DEFAULT_QUOTA (512 KB) if no entry exists.\n *\n * @param state - Current ACL state\n * @param identity - Napplet identity\n * @returns Quota in bytes\n *\n * @example\n * ```ts\n * getQuota(state, id); // 524288 (default 512 KB)\n * ```\n */\nexport function getQuota(state: AclState, identity: Identity): number {\n const key = toKey(identity);\n const entry = state.entries[key];\n return entry?.quota ?? DEFAULT_QUOTA;\n}\n\n/**\n * Serialize ACL state to a JSON string.\n *\n * Pure function — no I/O. The persistence adapter in @kehto/shell\n * uses this to write state to localStorage or other backends.\n *\n * @param state - ACL state to serialize\n * @returns JSON string representation\n *\n * @example\n * ```ts\n * const json = serialize(state);\n * localStorage.setItem('napplet:acl', json);\n * ```\n */\nexport function serialize(state: AclState): string {\n return JSON.stringify(state);\n}\n\n/**\n * Deserialize ACL state from a JSON string.\n *\n * Pure function — no I/O. Returns a valid AclState or a fresh\n * permissive state if the input is invalid.\n *\n * @param json - JSON string to parse\n * @returns Parsed AclState, or fresh permissive state on parse failure\n *\n * @example\n * ```ts\n * const json = localStorage.getItem('napplet:acl') ?? '';\n * const state = deserialize(json);\n * ```\n */\nexport function deserialize(json: string): AclState {\n try {\n const parsed = JSON.parse(json);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n (parsed.defaultPolicy === 'permissive' || parsed.defaultPolicy === 'restrictive') &&\n typeof parsed.entries === 'object' &&\n parsed.entries !== null\n ) {\n const entries: Record<string, AclEntry> = {};\n for (const [key, value] of Object.entries(parsed.entries)) {\n const entry = value as Record<string, unknown>;\n if (\n typeof entry.caps === 'number' &&\n typeof entry.blocked === 'boolean' &&\n typeof entry.quota === 'number'\n ) {\n entries[key] = {\n caps: entry.caps,\n blocked: entry.blocked,\n quota: entry.quota,\n };\n }\n }\n return { defaultPolicy: parsed.defaultPolicy, entries };\n }\n } catch {\n // Invalid JSON — fall through to default\n }\n return createState('permissive');\n}\n","/**\n * @kehto/acl — ACL state migration utility.\n *\n * Provides a pure function to migrate persisted ACL state from the old\n * 3-segment composite key format (pubkey:dTag:hash) to the new 2-segment\n * format (dTag:hash) introduced in NIP-5D v0.1.0.\n *\n * No I/O, no side effects. Pure function: takes AclState, returns AclState.\n */\n\nimport type { AclState, AclEntry } from './types.js';\n\n/**\n * Migrate ACL state from old 3-segment key format to new 2-segment key format.\n *\n * Converts entries stored under 'pubkey:dTag:hash' keys to 'dTag:hash' keys.\n * If two old entries map to the same dTag:hash, merges them conservatively:\n * - caps: OR of both bitfields (never removes a granted capability)\n * - blocked: OR of both flags (blocks if either source was blocked)\n * - quota: MAX of both values (keeps the higher allocation)\n *\n * Idempotent: if no 3-segment keys are found, returns the original state\n * unchanged (same object reference).\n *\n * @param state - Current ACL state (may contain old-format entries)\n * @returns Migrated AclState with only 2-segment keys, or the original\n * state unchanged if no migration was needed\n *\n * @example\n * ```ts\n * const oldState = deserialize(localStorage.getItem('napplet:acl') ?? '');\n * const newState = migrateAclState(oldState);\n * if (newState !== oldState) {\n * // Migration occurred — persist the new format\n * localStorage.setItem('napplet:acl', serialize(newState));\n * }\n * ```\n */\nexport function migrateAclState(state: AclState): AclState {\n const newEntries: Record<string, AclEntry> = {};\n let migrated = false;\n\n for (const [key, entry] of Object.entries(state.entries)) {\n const parts = key.split(':');\n if (parts.length === 3) {\n // Old format: pubkey:dTag:hash -> dTag:hash\n const newKey = `${parts[1]}:${parts[2]}`;\n const existing = newEntries[newKey];\n if (existing) {\n // Merge: union caps, preserve block, max quota\n newEntries[newKey] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[newKey] = entry;\n }\n migrated = true;\n } else {\n // Already new format or other key — merge if collision with a previously migrated entry\n const existing = newEntries[key];\n if (existing) {\n // Collision: old-format entry was processed first under the same key — merge\n newEntries[key] = {\n caps: existing.caps | entry.caps,\n blocked: existing.blocked || entry.blocked,\n quota: Math.max(existing.quota, entry.quota),\n };\n } else {\n newEntries[key] = entry;\n }\n }\n }\n\n if (!migrated) return state; // No old entries found — return original unchanged\n\n return { defaultPolicy: state.defaultPolicy, entries: newEntries };\n}\n","/**\n * @kehto/acl — NAP/NAP domain capability resolution (8 canonical + config,\n * resource, cvm, outbox, upload, intent).\n *\n * Maps NAP message types (e.g., 'relay.subscribe', 'identity.getProfile') to\n * the capability strings required by sender and recipient. This is the\n * canonical source for \"which capability does this NAP operation require?\"\n * in the @kehto/acl package.\n *\n * Canonical NIP-5D 8 domains: identity, keys, media, notify, relay,\n * storage, inc, theme. Extended in v1.7 with: config (Phase 39, 9th domain),\n * resource (Phase 40, 10th domain). The v1.1 `signer` domain is REMOVED —\n * getPublicKey/getRelays migrated to `identity`; signEvent/nip04/nip44 have\n * no napplet-visible surface (shell handles encryption inside\n * `relay.publishEncrypted`).\n *\n * Zero dependencies. No imports from @napplet/core or any external package.\n *\n * @see packages/acl/src/capabilities.ts for cap string constants + ALL_CAPABILITIES.\n * @see docs/ACL-MIGRATION.md section 2 — Capability Constant to NAP Domain Mapping.\n */\n\n/**\n * Minimal message shape used for capability resolution.\n *\n * Compatible with NappletMessage from @napplet/core, but defined here\n * independently to maintain @kehto/acl's zero-dependency constraint.\n *\n * @param type - NAP message type, e.g. 'relay.subscribe', 'identity.getProfile'\n */\nexport interface NapMessage {\n readonly type: string;\n}\n\n/**\n * Result of resolving what capabilities a NAP message requires.\n *\n * | Field | Description |\n * |----------------|----------------------------------------------------------------|\n * | `senderCap` | Capability the sender must have, or null if no check needed |\n * | `recipientCap` | Capability the recipient must have, or null if no check needed |\n *\n * @param senderCap - Capability the sender must have, or null if no ACL gate required\n * @param recipientCap - Capability the recipient must have, or null if no recipient check\n */\nexport interface CapabilityResolution {\n readonly senderCap: string | null;\n readonly recipientCap: string | null;\n}\n\n/**\n * `relay.*` — split publish / publishEncrypted / read actions.\n *\n * - `publish` → sender `relay:write`, recipient `relay:read`.\n * - `publishEncrypted` → sender `relay:write`, recipient `null` (the shell\n * handles encryption internally; no napplet-visible recipient ACL check).\n * - `subscribe` / `query` / `close` (and `.result` / `event` / `eose` /\n * `closed` / `publish.result` / `publishEncrypted.result`) → sender\n * `relay:read`, recipient `null`.\n */\nfunction relayMap(action: string): CapabilityResolution {\n if (action === 'publish') return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n if (action === 'publishEncrypted') return { senderCap: 'relay:write', recipientCap: null };\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `identity.*` — split shell-public reads from gated profile reads.\n *\n * Identity is strictly read-only per NAP-IDENTITY: napplets cannot sign,\n * encrypt, or decrypt (encryption is delegated via `relay.publishEncrypted`).\n *\n * - `getPublicKey` / `getRelays` → `null`/`null` (shell-public info).\n * - `getProfile` / `getFollows` / `getList` / `getZaps` / `getMutes` /\n * `getBlocked` / `getBadges` (and any other identity read) → sender\n * `identity:read`, recipient `null`.\n */\nfunction identityMap(action: string): CapabilityResolution {\n if (action === 'getPublicKey' || action === 'getRelays') {\n return { senderCap: null, recipientCap: null };\n }\n return { senderCap: 'identity:read', recipientCap: null };\n}\n\n/**\n * `keys.*` — split forwarding from binding lifecycle.\n *\n * - `forward` / `action` → `keys:forward`.\n * - `registerAction` / `unregisterAction` / `bindings` → `keys:bind`.\n */\nfunction keysMap(action: string): CapabilityResolution {\n if (action === 'forward' || action === 'action') {\n return { senderCap: 'keys:forward', recipientCap: null };\n }\n return { senderCap: 'keys:bind', recipientCap: null };\n}\n\n/**\n * `notify.*` — split channel/permission registration from send/interaction.\n *\n * - `channel.register` / `permission.request` / `permission.result` → `notify:channel`.\n * - `send` / `dismiss` / `badge` / `send.result` / `action` / `clicked` /\n * `dismissed` / `controls` (and any other notify action) → `notify:send`.\n */\nfunction notifyMap(action: string): CapabilityResolution {\n if (\n action === 'channel.register' ||\n action === 'permission.request' ||\n action === 'permission.result'\n ) {\n return { senderCap: 'notify:channel', recipientCap: null };\n }\n return { senderCap: 'notify:send', recipientCap: null };\n}\n\n/**\n * `storage.*` — narrowed to the canonical 4 actions (get/keys/set/remove).\n *\n * - `get` / `keys` → `state:read`.\n * - `set` / `remove` → `state:write`.\n * - anything else (incl. the removed `clear`) → `null`/`null`. The runtime\n * storage handler rejects non-canonical actions before ACL resolution so\n * napplets see the explicit rejection rather than a misleading cap denial.\n */\nfunction storageMap(action: string): CapabilityResolution {\n if (action === 'get' || action === 'keys') return { senderCap: 'state:read', recipientCap: null };\n if (action === 'set' || action === 'remove') return { senderCap: 'state:write', recipientCap: null };\n return { senderCap: null, recipientCap: null };\n}\n\n/**\n * `inc.*` — topic + channel sub-protocol.\n *\n * - Write actions (`emit`, `channel.emit`, `channel.broadcast`) → sender\n * `relay:write`, recipient `relay:read`. Semantically equivalent to relay\n * publish: point-to-point or fan-out writes gate on relay-write at wire\n * level even though channel membership ACL is enforced at `channel.open`.\n * - Read / control actions (`subscribe`, `unsubscribe`, `channel.open`,\n * `channel.list`, `channel.close`) → sender\n * `relay:read`, recipient `null`. Channel open-time ACL semantics: the\n * caller must already hold `relay:read`, and channel membership is\n * recorded by the inc handler.\n */\nfunction incMap(action: string): CapabilityResolution {\n if (action === 'emit' || action === 'channel.emit' || action === 'channel.broadcast') {\n return { senderCap: 'relay:write', recipientCap: 'relay:read' };\n }\n return { senderCap: 'relay:read', recipientCap: null };\n}\n\n/**\n * `config.*` — NAP-CONFIG reference service (v1.7 Phase 39 / 9th NAP domain).\n *\n * Asymmetric protocol: napplet reads, shell writes. ALL napplet-originated\n * config messages require `config:read`. Shell→napplet pushes\n * (`config.values`, `config.registerSchema.result`, `config.schemaError`)\n * are gated by the recipient's `config:read` cap.\n *\n * Anti-overlap: NAP-STORAGE remains the general key-value surface\n * (`state:read`/`state:write`). NAP-CONFIG is shell-managed per-napplet\n * configuration only — see CONFIG-04 scope boundary docs.\n */\nfunction configMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold config:read to see them).\n if (action === 'values' || action === 'registerSchema.result' || action === 'schemaError') {\n return { senderCap: null, recipientCap: 'config:read' };\n }\n // Napplet-originated requests: sender gate.\n return { senderCap: 'config:read', recipientCap: null };\n}\n\n/**\n * `resource.*` — NAP-RESOURCE authenticated fetch proxy (v1.7 Phase 40 / 10th NAP domain).\n *\n * Asymmetric protocol: napplet initiates fetch requests, shell proxies and responds.\n *\n * - `bytes` / `bytesMany` / `cancel` (napplet → shell requests) →\n * sender `resource:fetch`, recipient `null`. The napplet must hold\n * `resource:fetch` to issue resource requests or cancel one.\n * - `bytes.result` / `bytes.error` / `bytesMany.result` /\n * `bytesMany.error` (shell → napplet pushes) →\n * sender `null`, recipient `resource:fetch`. The napplet must hold\n * `resource:fetch` to receive the result/error push.\n * - Unknown resource.* actions → sender `resource:fetch`, recipient `null`\n * (default sender gate: napplet must hold resource:fetch to send anything\n * in the resource domain).\n */\nfunction resourceMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold resource:fetch to see them).\n if (action === 'bytes.result' || action === 'bytes.error' || action === 'bytesMany.result' || action === 'bytesMany.error') {\n return { senderCap: null, recipientCap: 'resource:fetch' };\n }\n // Napplet-originated requests: sender gate (bytes, cancel, and any unknown).\n return { senderCap: 'resource:fetch', recipientCap: null };\n}\n\n/**\n * `cvm.*` — NAP-CVM ContextVM bridge. Single `cvm:call` cap gates the domain.\n *\n * - `discover` / `request` / `close` (napplet → shell requests) →\n * sender `cvm:call`, recipient `null`. The napplet must hold `cvm:call`\n * to query servers or send MCP messages.\n * - `discover.result` / `request.result` / `close.result` / `event`\n * (shell → napplet pushes) → sender `null`, recipient `cvm:call`. The push\n * is gated against the receiving napplet's cap so a napplet without\n * `cvm:call` never sees CVM results or server-pushed events.\n * - Unknown `cvm.*` actions → sender `cvm:call` (default sender gate).\n */\nfunction cvmMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (action === 'event' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'cvm:call' };\n }\n // Napplet-originated requests: sender gate (discover, request, close, unknown).\n return { senderCap: 'cvm:call', recipientCap: null };\n}\n\n/**\n * `outbox.*` — NAP-OUTBOX outbox-aware relay routing (12th NAP domain).\n *\n * Split read/write like the `relay` domain, but with dedicated caps so a shell\n * can grant outbox routing independently of raw relay access:\n *\n * - `publish` (napplet → shell) → sender `outbox:write`,\n * recipient `null`. The shell signs and fans the event out to the relevant\n * write relays; there is no napplet-visible recipient ACL check.\n * - `query` / `subscribe` / `close` / `resolveRelays` (and any other\n * napplet-originated request) → sender `outbox:read`,\n * recipient `null`.\n * - `event` / `eose` / `closed` / `*.result` / `*.error` (shell → napplet\n * pushes) → sender `null`,\n * recipient `outbox:read`. The push is gated against the receiving napplet's\n * cap so a napplet without `outbox:read` never sees results or streamed\n * events.\n */\nfunction outboxMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold outbox:read to see them).\n if (\n action === 'event' ||\n action === 'eose' ||\n action === 'closed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'outbox:read' };\n }\n // Publish is the write op.\n if (action === 'publish') return { senderCap: 'outbox:write', recipientCap: null };\n // Napplet-originated reads: query, subscribe, close, resolveRelays, unknown.\n return { senderCap: 'outbox:read', recipientCap: null };\n}\n\n/**\n * `upload.*` — NAP-UPLOAD shell-mediated file/blob upload (13th NAP domain).\n *\n * A single `upload:write` cap gates the domain — uploading is the sensitive op\n * (network egress + identity-linking), and `status` only inspects the requesting\n * napplet's own uploads, so it rides the same grant rather than a separate read\n * cap.\n *\n * - `upload` / `status` (and any other napplet-originated request) → sender\n * `upload:write`, recipient `null`.\n * - `upload.result` / `status.result` / `status.changed` / `*.error` (shell →\n * napplet pushes) → sender `null`, recipient `upload:write`. The push is gated\n * against the receiving napplet's cap so a napplet without `upload:write`\n * never sees results or progress updates.\n */\nfunction uploadMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate.\n if (\n action === 'status.changed' ||\n action.endsWith('.result') ||\n action.endsWith('.error')\n ) {\n return { senderCap: null, recipientCap: 'upload:write' };\n }\n // Napplet-originated requests: sender gate (upload, status, unknown).\n return { senderCap: 'upload:write', recipientCap: null };\n}\n\n/**\n * `intent.*` — NAP-INTENT archetype intent dispatch (14th NAP domain).\n *\n * Split read/write so a read-only class can introspect available handlers but\n * cannot dispatch (mirrors `outbox` / `relay`, where the write op is the\n * sensitive one — here `invoke` is a focus-stealing cross-napplet navigation):\n *\n * - `invoke` (napplet → shell) → sender `intent:write`,\n * recipient `null`. The shell resolves the archetype to a handler, creates or\n * focuses its window, and delivers the payload.\n * - `available` / `handlers` (and any other napplet-originated request) →\n * sender `intent:read`, recipient `null`. Read-side catalog introspection.\n * - `changed` / `*.result` / `*.error` (shell → napplet pushes) →\n * sender `null`, recipient `intent:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `intent:read` never sees\n * availability updates or invoke results.\n */\nfunction intentMap(action: string): CapabilityResolution {\n // Shell-originated pushes: recipient gate (napplet must hold intent:read to see them).\n if (action === 'changed' || action.endsWith('.result') || action.endsWith('.error')) {\n return { senderCap: null, recipientCap: 'intent:read' };\n }\n // Invoke is the write op (cross-napplet dispatch / focus-steal).\n if (action === 'invoke') return { senderCap: 'intent:write', recipientCap: null };\n // Napplet-originated reads: available, handlers, unknown.\n return { senderCap: 'intent:read', recipientCap: null };\n}\n\n/**\n * `theme.*` — napplet read gate vs shell-initiated push.\n *\n * - `get` / `get.result` (and any other napplet-originated query) →\n * sender `theme:read`, recipient `null`.\n * - `changed` (shell → napplet push) →\n * sender `null`, recipient `theme:read`. The push is gated against the\n * receiving napplet's cap so a napplet without `theme:read` never sees\n * the update.\n *\n * Note: theme's runtime/service wiring lands in Phase 13. The ACL gate is\n * defined here in Phase 12 so the cap surface is canonical ahead of the\n * runtime work.\n */\nfunction themeMap(action: string): CapabilityResolution {\n if (action === 'changed') return { senderCap: null, recipientCap: 'theme:read' };\n return { senderCap: 'theme:read', recipientCap: null };\n}\n\n/**\n * Resolve the capabilities required by a NAP message.\n *\n * Splits `msg.type` on '.' to obtain `[domain, action]`, then dispatches to\n * a per-domain mapper. Unknown domains return `null/null` (silently ignored).\n *\n * **NAP domain mapping table (8 canonical domains):**\n *\n * | Domain | Action(s) | senderCap | recipientCap |\n * |------------|--------------------------------------------------------------|-----------------|---------------|\n * | `relay` | `subscribe`, `query`, `close`, results/pushes | `relay:read` | `null` |\n * | `relay` | `publish` | `relay:write` | `relay:read` |\n * | `relay` | `publishEncrypted` | `relay:write` | `null` |\n * | `identity` | `getPublicKey`, `getRelays` | `null` | `null` |\n * | `identity` | `getProfile/getFollows/getList/getZaps/getMutes/...` | `identity:read` | `null` |\n * | `keys` | `forward`, `action` | `keys:forward` | `null` |\n * | `keys` | `registerAction`, `unregisterAction`, `bindings` | `keys:bind` | `null` |\n * | `media` | any | `media:control` | `null` |\n * | `notify` | `channel.register`, `permission.request`, `permission.result` | `notify:channel`| `null` |\n * | `notify` | `send`, `dismiss`, `badge`, `clicked`, `action`, ... | `notify:send` | `null` |\n * | `storage` | `get`, `keys` | `state:read` | `null` |\n * | `storage` | `set`, `remove` | `state:write` | `null` |\n * | `storage` | any other (incl. removed `clear`) | `null` | `null` |\n * | `inc` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |\n * | `inc` | `subscribe`, `unsubscribe`, `channel.open/list/close` | `relay:read` | `null` |\n * | `theme` | `get`, `get.result` | `theme:read` | `null` |\n * | `theme` | `changed` (shell → napplet push) | `null` | `theme:read` |\n * | `config` | `get`, `subscribe`, `unsubscribe`, `registerSchema`, `openSettings` | `config:read` | `null` |\n * | `config` | `values`, `registerSchema.result`, `schemaError` (shell → napplet pushes) | `null` | `config:read` |\n * | `resource` | `bytes`, `bytesMany`, `cancel` (napplet → shell requests) | `resource:fetch`| `null` |\n * | `resource` | `bytes*.result`, `bytes*.error` (shell → napplet pushes) | `null` | `resource:fetch` |\n * | `intent` | `invoke` (napplet → shell) | `intent:write` | `null` |\n * | `intent` | `available`, `handlers` (napplet → shell) | `intent:read` | `null` |\n * | `intent` | `changed`, `*.result`, `*.error` (shell → napplet pushes) | `null` | `intent:read` |\n * | unknown | any | `null` | `null` |\n *\n * The `signer` domain is REMOVED — signer messages fall through to the\n * default null/null branch. `getPublicKey`/`getRelays` migrated to\n * `identity`; napplet-visible signing does not exist in NIP-5D (shell\n * signs internally for `relay.publishEncrypted`).\n *\n * @param msg - Message with a `type` field in NAP format (e.g., 'relay.subscribe')\n * @returns CapabilityResolution with senderCap and recipientCap (each may be null)\n *\n * @example\n * ```ts\n * resolveCapabilitiesNap({ type: 'relay.subscribe' })\n * // => { senderCap: 'relay:read', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'relay.publishEncrypted' })\n * // => { senderCap: 'relay:write', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'identity.getProfile' })\n * // => { senderCap: 'identity:read', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'keys.forward' })\n * // => { senderCap: 'keys:forward', recipientCap: null }\n *\n * resolveCapabilitiesNap({ type: 'inc.channel.broadcast' })\n * // => { senderCap: 'relay:write', recipientCap: 'relay:read' }\n *\n * resolveCapabilitiesNap({ type: 'theme.changed' })\n * // => { senderCap: null, recipientCap: 'theme:read' }\n *\n * resolveCapabilitiesNap({ type: 'signer.signEvent' })\n * // => { senderCap: null, recipientCap: null } // domain removed\n * ```\n */\nexport function resolveCapabilitiesNap(msg: NapMessage): CapabilityResolution {\n const dotIdx = msg.type.indexOf('.');\n if (dotIdx === -1) return { senderCap: null, recipientCap: null };\n const domain = msg.type.slice(0, dotIdx);\n const action = msg.type.slice(dotIdx + 1);\n\n switch (domain) {\n case 'relay': return relayMap(action);\n case 'identity': return identityMap(action);\n case 'keys': return keysMap(action);\n case 'media': return { senderCap: 'media:control', recipientCap: null };\n case 'notify': return notifyMap(action);\n case 'storage': return storageMap(action);\n case 'inc': return incMap(action);\n case 'theme': return themeMap(action);\n case 'config': return configMap(action);\n case 'resource': return resourceMap(action); // Phase 40 (RESOURCE-02)\n case 'cvm': return cvmMap(action); // NAP-CVM ContextVM bridge\n case 'outbox': return outboxMap(action); // NAP-OUTBOX outbox-aware relay routing\n case 'upload': return uploadMap(action); // NAP-UPLOAD shell-mediated file/blob upload\n case 'intent': return intentMap(action); // NAP-INTENT archetype intent dispatch\n default: return { senderCap: null, recipientCap: null };\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kehto/acl",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Pure, WASM-ready ACL module for the napplet protocol — zero dependencies, zero side effects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,16 +23,15 @@
23
23
  "publishConfig": {
24
24
  "access": "public"
25
25
  },
26
- "dependencies": {},
27
26
  "peerDependencies": {
28
- "@napplet/core": "^0.12.0",
29
- "@napplet/nap": "^0.12.0"
27
+ "@napplet/core": ">=0.20.0 <0.21.0",
28
+ "@napplet/nap": ">=0.20.0 <0.21.0"
30
29
  },
31
30
  "devDependencies": {
32
- "@napplet/core": "^0.12.0",
31
+ "@napplet/core": "^0.20.0",
32
+ "@napplet/nap": "^0.20.0",
33
33
  "tsup": "^8.5.0",
34
- "typescript": "^5.9.3",
35
- "@napplet/nap": "^0.12.0"
34
+ "typescript": "^5.9.3"
36
35
  },
37
36
  "license": "MIT",
38
37
  "repository": {