@kehto/acl 0.5.0 → 0.7.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/dist/capabilities.d.ts +8 -2
- package/dist/capabilities.js +9 -3
- package/dist/{chunk-T3LIC4XI.js → chunk-42FQ5SR6.js} +17 -3
- package/dist/chunk-42FQ5SR6.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +20 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-T3LIC4XI.js.map +0 -1
package/dist/capabilities.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* napplet-visible signing exists in canonical NIP-5D; signing flows
|
|
8
8
|
* through shell-internal `relay.publishEncrypted` instead.
|
|
9
9
|
*/
|
|
10
|
-
declare const ALL_CAPABILITIES: readonly ["relay:read", "relay:write", "cache:read", "cache:write", "hotkey:forward", "state:read", "state:write", "identity:read", "keys:bind", "keys:forward", "media:control", "notify:send", "notify:channel", "theme:read", "config:read", "resource:fetch", "identity:decrypt", "cvm:call"];
|
|
10
|
+
declare const ALL_CAPABILITIES: readonly ["relay:read", "relay:write", "cache:read", "cache:write", "hotkey:forward", "state:read", "state:write", "identity:read", "keys:bind", "keys:forward", "media:control", "notify:send", "notify:channel", "theme:read", "config:read", "resource:fetch", "identity:decrypt", "cvm:call", "outbox:read", "outbox:write", "upload:write"];
|
|
11
11
|
/** Union of every capability string in ALL_CAPABILITIES. */
|
|
12
12
|
type Capability = typeof ALL_CAPABILITIES[number];
|
|
13
13
|
/** identity.getProfile/getFollows/getList/getZaps/getMutes/getBlocked/getBadges */
|
|
@@ -32,5 +32,11 @@ declare const CAP_CONFIG_READ: "config:read";
|
|
|
32
32
|
declare const CAP_RESOURCE_FETCH: "resource:fetch";
|
|
33
33
|
/** cvm.discover / cvm.request / cvm.close (inbound) + cvm.*.result / cvm.event (outbound) */
|
|
34
34
|
declare const CAP_CVM_CALL: "cvm:call";
|
|
35
|
+
/** outbox.query / outbox.subscribe / outbox.close / outbox.resolveRelays (read-side outbox access) */
|
|
36
|
+
declare const CAP_OUTBOX_READ: "outbox:read";
|
|
37
|
+
/** outbox.publish (shell-signed, outbox-aware publish fanout) */
|
|
38
|
+
declare const CAP_OUTBOX_WRITE: "outbox:write";
|
|
39
|
+
/** upload.upload / upload.status (shell-mediated file/blob upload + status query) */
|
|
40
|
+
declare const CAP_UPLOAD_WRITE: "upload:write";
|
|
35
41
|
|
|
36
|
-
export { ALL_CAPABILITIES, CAP_CONFIG_READ, CAP_CVM_CALL, CAP_IDENTITY_DECRYPT, CAP_IDENTITY_READ, CAP_KEYS_BIND, CAP_KEYS_FORWARD, CAP_MEDIA_CONTROL, CAP_NOTIFY_CHANNEL, CAP_NOTIFY_SEND, CAP_RESOURCE_FETCH, CAP_THEME_READ, type Capability };
|
|
42
|
+
export { ALL_CAPABILITIES, CAP_CONFIG_READ, CAP_CVM_CALL, CAP_IDENTITY_DECRYPT, CAP_IDENTITY_READ, CAP_KEYS_BIND, CAP_KEYS_FORWARD, CAP_MEDIA_CONTROL, CAP_NOTIFY_CHANNEL, CAP_NOTIFY_SEND, CAP_OUTBOX_READ, CAP_OUTBOX_WRITE, CAP_RESOURCE_FETCH, CAP_THEME_READ, CAP_UPLOAD_WRITE, type Capability };
|
package/dist/capabilities.js
CHANGED
|
@@ -9,9 +9,12 @@ import {
|
|
|
9
9
|
CAP_MEDIA_CONTROL,
|
|
10
10
|
CAP_NOTIFY_CHANNEL,
|
|
11
11
|
CAP_NOTIFY_SEND,
|
|
12
|
+
CAP_OUTBOX_READ,
|
|
13
|
+
CAP_OUTBOX_WRITE,
|
|
12
14
|
CAP_RESOURCE_FETCH,
|
|
13
|
-
CAP_THEME_READ
|
|
14
|
-
|
|
15
|
+
CAP_THEME_READ,
|
|
16
|
+
CAP_UPLOAD_WRITE
|
|
17
|
+
} from "./chunk-42FQ5SR6.js";
|
|
15
18
|
export {
|
|
16
19
|
ALL_CAPABILITIES,
|
|
17
20
|
CAP_CONFIG_READ,
|
|
@@ -23,7 +26,10 @@ export {
|
|
|
23
26
|
CAP_MEDIA_CONTROL,
|
|
24
27
|
CAP_NOTIFY_CHANNEL,
|
|
25
28
|
CAP_NOTIFY_SEND,
|
|
29
|
+
CAP_OUTBOX_READ,
|
|
30
|
+
CAP_OUTBOX_WRITE,
|
|
26
31
|
CAP_RESOURCE_FETCH,
|
|
27
|
-
CAP_THEME_READ
|
|
32
|
+
CAP_THEME_READ,
|
|
33
|
+
CAP_UPLOAD_WRITE
|
|
28
34
|
};
|
|
29
35
|
//# sourceMappingURL=capabilities.js.map
|
|
@@ -23,7 +23,15 @@ var ALL_CAPABILITIES = [
|
|
|
23
23
|
// v1.8 Phase 45 — NUB-IDENTITY decrypt gate:
|
|
24
24
|
"identity:decrypt",
|
|
25
25
|
// NAP-CVM — ContextVM bridge (11th domain): call MCP-over-Nostr servers.
|
|
26
|
-
"cvm:call"
|
|
26
|
+
"cvm:call",
|
|
27
|
+
// NAP-OUTBOX — outbox-aware relay routing (12th domain): read = query/
|
|
28
|
+
// subscribe/resolveRelays/close; write = publish (shell-signed fanout).
|
|
29
|
+
"outbox:read",
|
|
30
|
+
"outbox:write",
|
|
31
|
+
// NAP-UPLOAD — shell-mediated file/blob upload (13th domain): a single write
|
|
32
|
+
// cap gates the network-egress + identity-linking upload op; status queries
|
|
33
|
+
// ride the same grant (a napplet only inspects its own uploads).
|
|
34
|
+
"upload:write"
|
|
27
35
|
];
|
|
28
36
|
var CAP_IDENTITY_READ = "identity:read";
|
|
29
37
|
var CAP_IDENTITY_DECRYPT = "identity:decrypt";
|
|
@@ -36,6 +44,9 @@ var CAP_THEME_READ = "theme:read";
|
|
|
36
44
|
var CAP_CONFIG_READ = "config:read";
|
|
37
45
|
var CAP_RESOURCE_FETCH = "resource:fetch";
|
|
38
46
|
var CAP_CVM_CALL = "cvm:call";
|
|
47
|
+
var CAP_OUTBOX_READ = "outbox:read";
|
|
48
|
+
var CAP_OUTBOX_WRITE = "outbox:write";
|
|
49
|
+
var CAP_UPLOAD_WRITE = "upload:write";
|
|
39
50
|
|
|
40
51
|
export {
|
|
41
52
|
ALL_CAPABILITIES,
|
|
@@ -49,6 +60,9 @@ export {
|
|
|
49
60
|
CAP_THEME_READ,
|
|
50
61
|
CAP_CONFIG_READ,
|
|
51
62
|
CAP_RESOURCE_FETCH,
|
|
52
|
-
CAP_CVM_CALL
|
|
63
|
+
CAP_CVM_CALL,
|
|
64
|
+
CAP_OUTBOX_READ,
|
|
65
|
+
CAP_OUTBOX_WRITE,
|
|
66
|
+
CAP_UPLOAD_WRITE
|
|
53
67
|
};
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
68
|
+
//# sourceMappingURL=chunk-42FQ5SR6.js.map
|
|
@@ -0,0 +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 // v1.8 Phase 45 — NUB-IDENTITY decrypt gate:\n 'identity:decrypt',\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] 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/** identity.decrypt (class-1 only; shell-mediated decrypt) */\nexport const CAP_IDENTITY_DECRYPT = 'identity:decrypt' 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"],"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,EAEA;AAAA;AAAA;AAAA,EAGA;AAAA,EAAe;AAAA;AAAA;AAAA;AAAA,EAIf;AACF;AAMO,IAAM,oBAAsB;AAE5B,IAAM,uBAAuB;AAE7B,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;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -323,7 +323,8 @@ declare function deserialize(json: string): AclState;
|
|
|
323
323
|
declare function migrateAclState(state: AclState): AclState;
|
|
324
324
|
|
|
325
325
|
/**
|
|
326
|
-
* @kehto/acl — NUB domain capability resolution (
|
|
326
|
+
* @kehto/acl — NUB/NAP domain capability resolution (8 canonical + config,
|
|
327
|
+
* resource, cvm, outbox).
|
|
327
328
|
*
|
|
328
329
|
* Maps NUB message types (e.g., 'relay.subscribe', 'identity.getProfile') to
|
|
329
330
|
* the capability strings required by sender and recipient. This is the
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
CAP_NOTIFY_CHANNEL,
|
|
10
10
|
CAP_NOTIFY_SEND,
|
|
11
11
|
CAP_THEME_READ
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-42FQ5SR6.js";
|
|
13
13
|
|
|
14
14
|
// src/types.ts
|
|
15
15
|
var CAP_RELAY_READ = 1 << 0;
|
|
@@ -232,6 +232,19 @@ function cvmMap(action) {
|
|
|
232
232
|
}
|
|
233
233
|
return { senderCap: "cvm:call", recipientCap: null };
|
|
234
234
|
}
|
|
235
|
+
function outboxMap(action) {
|
|
236
|
+
if (action === "event" || action === "eose" || action === "closed" || action.endsWith(".result") || action.endsWith(".error")) {
|
|
237
|
+
return { senderCap: null, recipientCap: "outbox:read" };
|
|
238
|
+
}
|
|
239
|
+
if (action === "publish") return { senderCap: "outbox:write", recipientCap: null };
|
|
240
|
+
return { senderCap: "outbox:read", recipientCap: null };
|
|
241
|
+
}
|
|
242
|
+
function uploadMap(action) {
|
|
243
|
+
if (action === "status.changed" || action.endsWith(".result") || action.endsWith(".error")) {
|
|
244
|
+
return { senderCap: null, recipientCap: "upload:write" };
|
|
245
|
+
}
|
|
246
|
+
return { senderCap: "upload:write", recipientCap: null };
|
|
247
|
+
}
|
|
235
248
|
function themeMap(action) {
|
|
236
249
|
if (action === "changed") return { senderCap: null, recipientCap: "theme:read" };
|
|
237
250
|
return { senderCap: "theme:read", recipientCap: null };
|
|
@@ -266,6 +279,12 @@ function resolveCapabilitiesNub(msg) {
|
|
|
266
279
|
case "cvm":
|
|
267
280
|
return cvmMap(action);
|
|
268
281
|
// NAP-CVM ContextVM bridge
|
|
282
|
+
case "outbox":
|
|
283
|
+
return outboxMap(action);
|
|
284
|
+
// NAP-OUTBOX outbox-aware relay routing
|
|
285
|
+
case "upload":
|
|
286
|
+
return uploadMap(action);
|
|
287
|
+
// NAP-UPLOAD shell-mediated file/blob upload
|
|
269
288
|
default:
|
|
270
289
|
return { senderCap: null, recipientCap: null };
|
|
271
290
|
}
|
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 domain capability resolution (10-domain: 8 canonical + config + resource).\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 * - `getPublicKey` / `getRelays` → `null`/`null` (shell-public info).\n * - `decrypt` → sender `identity:decrypt` (class-1 only).\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 if (action === 'decrypt') {\n return { senderCap: 'identity:decrypt', 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.*` — 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 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 * `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` | `decrypt` | `identity:decrypt` | `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` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |\n * | `ifc` | `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 * | 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': return ifcMap(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 default: return { senderCap: null, recipientCap: null };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAQO,IAAM,iBAAoB,KAAK;AAE/B,IAAM,kBAAoB,KAAK;AAE/B,IAAM,iBAAoB,KAAK;AAE/B,IAAM,kBAAoB,KAAK;AAE/B,IAAM,qBAAqB,KAAK;AAEhC,IAAM,iBAAoB,KAAK;AAE/B,IAAM,iBAAoB,KAAK;AAE/B,IAAM,iBAAoB,KAAK;AAE/B,IAAM,iBAAoB,KAAK;AAE/B,IAAM,kBAAoB,KAAK;AAG/B,IAAM,WAAW,KAAK,MAAM;AAG5B,IAAM,WAAW;AAgDjB,IAAM,gBAAgB,MAAM;;;ACzD5B,SAAS,MAAM,UAA4B;AAChD,SAAO,GAAG,SAAS,IAAI,IAAI,SAAS,IAAI;AAC1C;AA+BO,SAAS,MAAM,OAAiB,UAAoB,KAAsB;AAC/E,QAAM,MAAM,MAAM,QAAQ;AAC1B,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,MAAI,CAAC,OAAO;AACV,WAAO,MAAM,kBAAkB;AAAA,EACjC;AACA,MAAI,MAAM,SAAS;AACjB,WAAO;AAAA,EACT;AACA,UAAQ,MAAM,OAAO,SAAS;AAChC;;;AC1CO,SAAS,YAAY,SAAuC,cAAwB;AACzF,SAAO,EAAE,eAAe,QAAQ,SAAS,CAAC,EAAE;AAC9C;AAMA,SAAS,SAAS,OAAiB,KAAuB;AACxD,QAAM,WAAW,MAAM,QAAQ,GAAG;AAClC,MAAI,SAAU,QAAO;AAErB,SAAO;AAAA,IACL,MAAM,MAAM,kBAAkB,eAAe,UAAU;AAAA,IACvD,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACF;AAmBO,SAAS,MAAM,OAAiB,UAAoB,KAAuB;AAChF,QAAM,MAAM,MAAM,QAAQ;AAC1B,QAAM,QAAQ,SAAS,OAAO,GAAG;AACjC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,MAAM;AAAA,MACT,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO,IAAI;AAAA,IAC5C;AAAA,EACF;AACF;AAmBO,SAAS,OAAO,OAAiB,UAAoB,KAAuB;AACjF,QAAM,MAAM,MAAM,QAAQ;AAC1B,QAAM,QAAQ,SAAS,OAAO,GAAG;AACjC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,MAAM;AAAA,MACT,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,MAAM,MAAM,OAAO,CAAC,IAAI;AAAA,IAC7C;AAAA,EACF;AACF;AAmBO,SAAS,MAAM,OAAiB,UAA8B;AACnE,QAAM,MAAM,MAAM,QAAQ;AAC1B,QAAM,QAAQ,SAAS,OAAO,GAAG;AACjC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,MAAM;AAAA,MACT,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,SAAS,KAAK;AAAA,IACnC;AAAA,EACF;AACF;AAkBO,SAAS,QAAQ,OAAiB,UAA8B;AACrE,QAAM,MAAM,MAAM,QAAQ;AAC1B,QAAM,QAAQ,SAAS,OAAO,GAAG;AACjC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,MAAM;AAAA,MACT,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,SAAS,MAAM;AAAA,IACpC;AAAA,EACF;AACF;AAgBO,SAAS,SAAS,OAAiB,UAAoB,OAAyB;AACrF,QAAM,MAAM,MAAM,QAAQ;AAC1B,QAAM,QAAQ,SAAS,OAAO,GAAG;AACjC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS;AAAA,MACP,GAAG,MAAM;AAAA,MACT,CAAC,GAAG,GAAG,EAAE,GAAG,OAAO,OAAO,MAAM;AAAA,IAClC;AAAA,EACF;AACF;AAgBO,SAAS,SAAS,OAAiB,UAA4B;AACpE,QAAM,MAAM,MAAM,QAAQ;AAC1B,QAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,SAAO,OAAO,SAAS;AACzB;AAiBO,SAAS,UAAU,OAAyB;AACjD,SAAO,KAAK,UAAU,KAAK;AAC7B;AAiBO,SAAS,YAAY,MAAwB;AAClD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QACE,OAAO,WAAW,YAClB,WAAW,SACV,OAAO,kBAAkB,gBAAgB,OAAO,kBAAkB,kBACnE,OAAO,OAAO,YAAY,YAC1B,OAAO,YAAY,MACnB;AACA,YAAM,UAAoC,CAAC;AAC3C,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG;AACzD,cAAM,QAAQ;AACd,YACE,OAAO,MAAM,SAAS,YACtB,OAAO,MAAM,YAAY,aACzB,OAAO,MAAM,UAAU,UACvB;AACA,kBAAQ,GAAG,IAAI;AAAA,YACb,MAAM,MAAM;AAAA,YACZ,SAAS,MAAM;AAAA,YACf,OAAO,MAAM;AAAA,UACf;AAAA,QACF;AAAA,MACF;AACA,aAAO,EAAE,eAAe,OAAO,eAAe,QAAQ;AAAA,IACxD;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,YAAY,YAAY;AACjC;;;ACvOO,SAAS,gBAAgB,OAA2B;AACzD,QAAM,aAAuC,CAAC;AAC9C,MAAI,WAAW;AAEf,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,OAAO,GAAG;AACxD,UAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAI,MAAM,WAAW,GAAG;AAEtB,YAAM,SAAS,GAAG,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AACtC,YAAM,WAAW,WAAW,MAAM;AAClC,UAAI,UAAU;AAEZ,mBAAW,MAAM,IAAI;AAAA,UACnB,MAAM,SAAS,OAAO,MAAM;AAAA,UAC5B,SAAS,SAAS,WAAW,MAAM;AAAA,UACnC,OAAO,KAAK,IAAI,SAAS,OAAO,MAAM,KAAK;AAAA,QAC7C;AAAA,MACF,OAAO;AACL,mBAAW,MAAM,IAAI;AAAA,MACvB;AACA,iBAAW;AAAA,IACb,OAAO;AAEL,YAAM,WAAW,WAAW,GAAG;AAC/B,UAAI,UAAU;AAEZ,mBAAW,GAAG,IAAI;AAAA,UAChB,MAAM,SAAS,OAAO,MAAM;AAAA,UAC5B,SAAS,SAAS,WAAW,MAAM;AAAA,UACnC,OAAO,KAAK,IAAI,SAAS,OAAO,MAAM,KAAK;AAAA,QAC7C;AAAA,MACF,OAAO;AACL,mBAAW,GAAG,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO,EAAE,eAAe,MAAM,eAAe,SAAS,WAAW;AACnE;;;ACnBA,SAAS,SAAS,QAAsC;AACtD,MAAI,WAAW,UAAW,QAAO,EAAE,WAAW,eAAe,cAAc,aAAa;AACxF,MAAI,WAAW,mBAAoB,QAAO,EAAE,WAAW,eAAe,cAAc,KAAK;AACzF,SAAO,EAAE,WAAW,cAAc,cAAc,KAAK;AACvD;AAWA,SAAS,YAAY,QAAsC;AACzD,MAAI,WAAW,kBAAkB,WAAW,aAAa;AACvD,WAAO,EAAE,WAAW,MAAM,cAAc,KAAK;AAAA,EAC/C;AACA,MAAI,WAAW,WAAW;AACxB,WAAO,EAAE,WAAW,oBAAoB,cAAc,KAAK;AAAA,EAC7D;AACA,SAAO,EAAE,WAAW,iBAAiB,cAAc,KAAK;AAC1D;AAQA,SAAS,QAAQ,QAAsC;AACrD,MAAI,WAAW,aAAa,WAAW,UAAU;AAC/C,WAAO,EAAE,WAAW,gBAAgB,cAAc,KAAK;AAAA,EACzD;AACA,SAAO,EAAE,WAAW,aAAa,cAAc,KAAK;AACtD;AASA,SAAS,UAAU,QAAsC;AACvD,MACE,WAAW,sBACX,WAAW,wBACX,WAAW,qBACX;AACA,WAAO,EAAE,WAAW,kBAAkB,cAAc,KAAK;AAAA,EAC3D;AACA,SAAO,EAAE,WAAW,eAAe,cAAc,KAAK;AACxD;AAWA,SAAS,WAAW,QAAsC;AACxD,MAAI,WAAW,SAAS,WAAW,OAAQ,QAAO,EAAE,WAAW,cAAc,cAAc,KAAK;AAChG,MAAI,WAAW,SAAS,WAAW,SAAU,QAAO,EAAE,WAAW,eAAe,cAAc,KAAK;AACnG,SAAO,EAAE,WAAW,MAAM,cAAc,KAAK;AAC/C;AAeA,SAAS,OAAO,QAAsC;AACpD,MAAI,WAAW,UAAU,WAAW,kBAAkB,WAAW,qBAAqB;AACpF,WAAO,EAAE,WAAW,eAAe,cAAc,aAAa;AAAA,EAChE;AACA,SAAO,EAAE,WAAW,cAAc,cAAc,KAAK;AACvD;AAcA,SAAS,UAAU,QAAsC;AAEvD,MAAI,WAAW,YAAY,WAAW,2BAA2B,WAAW,eAAe;AACzF,WAAO,EAAE,WAAW,MAAM,cAAc,cAAc;AAAA,EACxD;AAEA,SAAO,EAAE,WAAW,eAAe,cAAc,KAAK;AACxD;AAiBA,SAAS,YAAY,QAAsC;AAEzD,MAAI,WAAW,kBAAkB,WAAW,eAAe;AACzD,WAAO,EAAE,WAAW,MAAM,cAAc,iBAAiB;AAAA,EAC3D;AAEA,SAAO,EAAE,WAAW,kBAAkB,cAAc,KAAK;AAC3D;AAcA,SAAS,OAAO,QAAsC;AAEpD,MAAI,WAAW,WAAW,OAAO,SAAS,SAAS,KAAK,OAAO,SAAS,QAAQ,GAAG;AACjF,WAAO,EAAE,WAAW,MAAM,cAAc,WAAW;AAAA,EACrD;AAEA,SAAO,EAAE,WAAW,YAAY,cAAc,KAAK;AACrD;AAgBA,SAAS,SAAS,QAAsC;AACtD,MAAI,WAAW,UAAW,QAAO,EAAE,WAAW,MAAM,cAAc,aAAa;AAC/E,SAAO,EAAE,WAAW,cAAc,cAAc,KAAK;AACvD;AAoEO,SAAS,uBAAuB,KAAuC;AAC5E,QAAM,SAAS,IAAI,KAAK,QAAQ,GAAG;AACnC,MAAI,WAAW,GAAI,QAAO,EAAE,WAAW,MAAM,cAAc,KAAK;AAChE,QAAM,SAAS,IAAI,KAAK,MAAM,GAAG,MAAM;AACvC,QAAM,SAAS,IAAI,KAAK,MAAM,SAAS,CAAC;AAExC,UAAQ,QAAQ;AAAA,IACd,KAAK;AAAY,aAAO,SAAS,MAAM;AAAA,IACvC,KAAK;AAAY,aAAO,YAAY,MAAM;AAAA,IAC1C,KAAK;AAAY,aAAO,QAAQ,MAAM;AAAA,IACtC,KAAK;AAAY,aAAO,EAAE,WAAW,iBAAiB,cAAc,KAAK;AAAA,IACzE,KAAK;AAAY,aAAO,UAAU,MAAM;AAAA,IACxC,KAAK;AAAY,aAAO,WAAW,MAAM;AAAA,IACzC,KAAK;AAAY,aAAO,OAAO,MAAM;AAAA,IACrC,KAAK;AAAY,aAAO,SAAS,MAAM;AAAA,IACvC,KAAK;AAAY,aAAO,UAAU,MAAM;AAAA,IACxC,KAAK;AAAY,aAAO,YAAY,MAAM;AAAA;AAAA,IAC1C,KAAK;AAAY,aAAO,OAAO,MAAM;AAAA;AAAA,IACrC;AAAiB,aAAO,EAAE,WAAW,MAAM,cAAc,KAAK;AAAA,EAChE;AACF;","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 — NUB/NAP domain capability resolution (8 canonical + config,\n * resource, cvm, outbox).\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 * - `getPublicKey` / `getRelays` → `null`/`null` (shell-public info).\n * - `decrypt` → sender `identity:decrypt` (class-1 only).\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 if (action === 'decrypt') {\n return { senderCap: 'identity:decrypt', 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.*` — 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 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 * `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` | `decrypt` | `identity:decrypt` | `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` | `emit`, `channel.emit`, `channel.broadcast` | `relay:write` | `relay:read` |\n * | `ifc` | `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 * | 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': return ifcMap(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 default: return { senderCap: null, recipientCap: null };\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
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 // v1.8 Phase 45 — NUB-IDENTITY decrypt gate:\n 'identity:decrypt',\n // NAP-CVM — ContextVM bridge (11th domain): call MCP-over-Nostr servers.\n 'cvm:call',\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/** identity.decrypt (class-1 only; shell-mediated decrypt) */\nexport const CAP_IDENTITY_DECRYPT = 'identity:decrypt' 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"],"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,EAEA;AACF;AAMO,IAAM,oBAAsB;AAE5B,IAAM,uBAAuB;AAE7B,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;","names":[]}
|