@tinycloud/sdk-services 3.7.0-beta.3 → 3.7.0-beta.4

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.
@@ -0,0 +1,1077 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/encryption/namedKeys.ts
21
+ var namedKeys_exports = {};
22
+ __export(namedKeys_exports, {
23
+ KEY_ADMIN_FACT_TYPE: () => KEY_ADMIN_FACT_TYPE,
24
+ KEY_DESCRIBE_FACT_TYPE: () => KEY_DESCRIBE_FACT_TYPE,
25
+ NAMED_KEY_ENVELOPE_VERSION: () => NAMED_KEY_ENVELOPE_VERSION,
26
+ NamedKeyClient: () => NamedKeyClient,
27
+ NamedKeyError: () => NamedKeyError,
28
+ buildKeyAdminFacts: () => buildKeyAdminFacts,
29
+ buildKeyDescribeFacts: () => buildKeyDescribeFacts,
30
+ mapNodeKeyError: () => mapNodeKeyError,
31
+ requireKeyTarget: () => requireKeyTarget,
32
+ safeEcho: () => safeEcho
33
+ });
34
+ module.exports = __toCommonJS(namedKeys_exports);
35
+
36
+ // src/encryption/canonical.ts
37
+ function canonicalize(value) {
38
+ if (value === void 0) {
39
+ return "";
40
+ }
41
+ return stringify(value);
42
+ }
43
+ function stringify(value) {
44
+ if (value === null) return "null";
45
+ switch (typeof value) {
46
+ case "boolean":
47
+ case "number":
48
+ return JSON.stringify(value);
49
+ case "string":
50
+ return JSON.stringify(value);
51
+ case "object": {
52
+ if (Array.isArray(value)) {
53
+ return `[${value.map(stringify).join(",")}]`;
54
+ }
55
+ const keys = Object.keys(value).sort();
56
+ const parts = [];
57
+ for (const k of keys) {
58
+ const v = value[k];
59
+ if (v === void 0) continue;
60
+ parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
61
+ }
62
+ return `{${parts.join(",")}}`;
63
+ }
64
+ default:
65
+ throw new TypeError(
66
+ `canonicalize: unsupported value type ${typeof value}`
67
+ );
68
+ }
69
+ }
70
+ var HEX = "0123456789abcdef";
71
+ function hexEncode(bytes) {
72
+ let out = "";
73
+ for (let i = 0; i < bytes.length; i++) {
74
+ const b = bytes[i];
75
+ out += HEX[b >> 4 & 15] + HEX[b & 15];
76
+ }
77
+ return out;
78
+ }
79
+ function base64Encode(bytes) {
80
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
81
+ let out = "";
82
+ for (let i = 0; i < bytes.length; i += 3) {
83
+ const b0 = bytes[i];
84
+ const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
85
+ const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
86
+ out += chars[b0 >> 2 & 63];
87
+ out += chars[(b0 << 4 | b1 >> 4) & 63];
88
+ out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
89
+ out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
90
+ }
91
+ return out;
92
+ }
93
+ function base64Decode(s) {
94
+ const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
95
+ const len = clean.length;
96
+ if (len % 4 !== 0) {
97
+ throw new Error("invalid base64 input");
98
+ }
99
+ const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
100
+ const outLen = len / 4 * 3 - padding;
101
+ const out = new Uint8Array(outLen);
102
+ const lookup = {};
103
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
104
+ for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
105
+ let outIdx = 0;
106
+ for (let i = 0; i < len; i += 4) {
107
+ const v0 = lookup[clean[i]] ?? 0;
108
+ const v1 = lookup[clean[i + 1]] ?? 0;
109
+ const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
110
+ const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
111
+ const b0 = v0 << 2 | v1 >> 4;
112
+ const b1 = (v1 & 15) << 4 | v2 >> 2;
113
+ const b2 = (v2 & 3) << 6 | v3;
114
+ if (outIdx < outLen) out[outIdx++] = b0;
115
+ if (outIdx < outLen) out[outIdx++] = b1;
116
+ if (outIdx < outLen) out[outIdx++] = b2;
117
+ }
118
+ return out;
119
+ }
120
+ function utf8Encode(s) {
121
+ return new TextEncoder().encode(s);
122
+ }
123
+ function canonicalHashHex(sha256, value) {
124
+ const canonical = canonicalize(value);
125
+ return hexEncode(sha256(utf8Encode(canonical)));
126
+ }
127
+
128
+ // src/encryption/networkId.ts
129
+ var URN_PREFIX = "urn:tinycloud:encryption:";
130
+ var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
131
+ var NetworkIdError = class extends Error {
132
+ constructor(message) {
133
+ super(message);
134
+ this.name = "NetworkIdError";
135
+ }
136
+ };
137
+ function parseNetworkId(networkId) {
138
+ if (typeof networkId !== "string" || networkId.length === 0) {
139
+ throw new NetworkIdError("networkId must be a non-empty string");
140
+ }
141
+ if (!networkId.startsWith(URN_PREFIX)) {
142
+ throw new NetworkIdError(
143
+ `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
144
+ );
145
+ }
146
+ const body = networkId.slice(URN_PREFIX.length);
147
+ const lastColon = body.lastIndexOf(":");
148
+ if (lastColon <= 0 || lastColon === body.length - 1) {
149
+ throw new NetworkIdError(
150
+ `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
151
+ );
152
+ }
153
+ const ownerDid = body.slice(0, lastColon);
154
+ const name = body.slice(lastColon + 1);
155
+ if (!ownerDid.startsWith("did:")) {
156
+ throw new NetworkIdError(
157
+ `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
158
+ );
159
+ }
160
+ const didParts = ownerDid.split(":");
161
+ if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
162
+ throw new NetworkIdError(
163
+ `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
164
+ );
165
+ }
166
+ if (!NETWORK_NAME_RE.test(name)) {
167
+ throw new NetworkIdError(
168
+ `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
169
+ );
170
+ }
171
+ return { networkId, ownerDid, name };
172
+ }
173
+ var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
174
+
175
+ // src/encryption/receiverKey.ts
176
+ function generateRandomReceiverKey(input) {
177
+ const seed = input.crypto.randomBytes(32);
178
+ return input.crypto.x25519FromSeed(seed);
179
+ }
180
+
181
+ // src/encryption/types.ts
182
+ var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
183
+ var DECRYPT_FACT_TYPE = "tinycloud.encryption.decrypt/v1";
184
+ var DECRYPT_RESULT_TYPE = "tinycloud.encryption.decrypt-result/v1";
185
+ var DECRYPT_ACTION = "tinycloud.encryption/decrypt";
186
+ function defaultEncryptionMessage(input) {
187
+ switch (input.code) {
188
+ case "NETWORK_NOT_FOUND":
189
+ return input.message ?? `Network not found: ${input.networkId ?? input.name ?? "<unknown>"}`;
190
+ case "NETWORK_NOT_ACTIVE":
191
+ return input.message ?? `Network not active (state=${input.state})`;
192
+ case "INVALID_NETWORK_ID":
193
+ return input.message;
194
+ case "INVALID_ENVELOPE":
195
+ return input.message;
196
+ case "DECRYPT_DENIED":
197
+ return input.message;
198
+ case "INVALID_RESPONSE":
199
+ return input.message;
200
+ case "RESPONSE_SIGNATURE_INVALID":
201
+ return input.message ?? "Node response signature failed to verify";
202
+ case "RESPONSE_BINDING_MISMATCH":
203
+ return input.message ?? `Node response binding mismatch on field ${JSON.stringify(input.field)}`;
204
+ case "TRANSPORT_ERROR":
205
+ return input.message ?? input.cause.message;
206
+ case "INVALID_INPUT":
207
+ return input.message;
208
+ }
209
+ }
210
+ function encryptionError(input) {
211
+ return {
212
+ ...input,
213
+ service: "encryption",
214
+ message: defaultEncryptionMessage(input)
215
+ };
216
+ }
217
+
218
+ // src/encryption/response.ts
219
+ function canonicalSignedResponse(response) {
220
+ const { nodeSignature: _drop, ...rest } = response;
221
+ return canonicalize(rest);
222
+ }
223
+ function verifyDecryptResponse(input) {
224
+ const { crypto, request, facts, invocationCid, requestBodyHash, response } = input;
225
+ if (response.type !== DECRYPT_RESULT_TYPE) {
226
+ return {
227
+ ok: false,
228
+ error: encryptionError({
229
+ code: "INVALID_RESPONSE",
230
+ message: `response.type must be ${DECRYPT_RESULT_TYPE}`
231
+ })
232
+ };
233
+ }
234
+ if (response.targetNode !== request.targetNode) {
235
+ return {
236
+ ok: false,
237
+ error: encryptionError({
238
+ code: "RESPONSE_BINDING_MISMATCH",
239
+ field: "targetNode"
240
+ })
241
+ };
242
+ }
243
+ if (response.networkId !== request.networkId) {
244
+ return {
245
+ ok: false,
246
+ error: encryptionError({
247
+ code: "RESPONSE_BINDING_MISMATCH",
248
+ field: "networkId"
249
+ })
250
+ };
251
+ }
252
+ if (response.nodeId !== request.targetNode) {
253
+ return {
254
+ ok: false,
255
+ error: encryptionError({
256
+ code: "RESPONSE_BINDING_MISMATCH",
257
+ field: "nodeId"
258
+ })
259
+ };
260
+ }
261
+ if (response.alg !== request.alg) {
262
+ return {
263
+ ok: false,
264
+ error: encryptionError({
265
+ code: "RESPONSE_BINDING_MISMATCH",
266
+ field: "alg"
267
+ })
268
+ };
269
+ }
270
+ if (response.keyVersion !== request.keyVersion) {
271
+ return {
272
+ ok: false,
273
+ error: encryptionError({
274
+ code: "RESPONSE_BINDING_MISMATCH",
275
+ field: "keyVersion"
276
+ })
277
+ };
278
+ }
279
+ if (response.encryptedSymmetricKeyHash !== request.encryptedSymmetricKeyHash) {
280
+ return {
281
+ ok: false,
282
+ error: encryptionError({
283
+ code: "RESPONSE_BINDING_MISMATCH",
284
+ field: "encryptedSymmetricKeyHash"
285
+ })
286
+ };
287
+ }
288
+ if (response.receiverPublicKeyHash !== request.receiverPublicKeyHash) {
289
+ return {
290
+ ok: false,
291
+ error: encryptionError({
292
+ code: "RESPONSE_BINDING_MISMATCH",
293
+ field: "receiverPublicKeyHash"
294
+ })
295
+ };
296
+ }
297
+ if (response.invocationCid !== invocationCid) {
298
+ return {
299
+ ok: false,
300
+ error: encryptionError({
301
+ code: "RESPONSE_BINDING_MISMATCH",
302
+ field: "invocationCid"
303
+ })
304
+ };
305
+ }
306
+ const expectedRequestHash = hexEncode(
307
+ crypto.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
308
+ );
309
+ if (response.requestHash !== expectedRequestHash) {
310
+ return {
311
+ ok: false,
312
+ error: encryptionError({
313
+ code: "RESPONSE_BINDING_MISMATCH",
314
+ field: "requestHash"
315
+ })
316
+ };
317
+ }
318
+ if (facts.encryptedSymmetricKeyHash !== response.encryptedSymmetricKeyHash || facts.receiverPublicKeyHash !== response.receiverPublicKeyHash || facts.networkId !== response.networkId || facts.targetNode !== response.targetNode || facts.alg !== response.alg || facts.keyVersion !== response.keyVersion) {
319
+ return {
320
+ ok: false,
321
+ error: encryptionError({
322
+ code: "RESPONSE_BINDING_MISMATCH",
323
+ field: "facts"
324
+ })
325
+ };
326
+ }
327
+ const signedBytes = new TextEncoder().encode(
328
+ canonicalSignedResponse(response)
329
+ );
330
+ const signatureBytes = base64Decode(response.nodeSignature);
331
+ if (!crypto.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
332
+ return {
333
+ ok: false,
334
+ error: encryptionError({
335
+ code: "RESPONSE_SIGNATURE_INVALID"
336
+ })
337
+ };
338
+ }
339
+ return { ok: true, data: response };
340
+ }
341
+ function openWrappedKey(crypto, receiverPrivateKey, response) {
342
+ const wrapped = base64Decode(response.wrappedKey);
343
+ return crypto.openWithReceiverKey(receiverPrivateKey, wrapped);
344
+ }
345
+
346
+ // src/encryption/resource.ts
347
+ var import_bootstrap = require("@tinycloud/bootstrap");
348
+ var SEGMENT_SOURCE = "[a-z0-9][a-z0-9._-]{0,63}";
349
+ var PATH_SOURCE = `${SEGMENT_SOURCE}(?:/${SEGMENT_SOURCE}){0,7}`;
350
+ var KEY_PATH_RE = new RegExp(`^${PATH_SOURCE}$`);
351
+ var KEY_NETWORK_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
352
+ var RESOURCE_RE = /^(did:[a-z0-9]+:[A-Za-z0-9._:-]+):([^:/]+)(?:\/(.*))?$/s;
353
+ var MAX_PATH_BYTES = 255;
354
+ var PKH_EIP155_PREFIX = "did:pkh:eip155:";
355
+ var MAX_U64 = 18446744073709551615n;
356
+ var KEY_CREATE_ABILITY = "tinycloud.encryption/key.create";
357
+ var KEY_BLOCK_ABILITY = "tinycloud.encryption/key.block";
358
+ var KEY_UNBLOCK_ABILITY = "tinycloud.encryption/key.unblock";
359
+ var DELEGABLE_KEY_ABILITIES = Object.freeze([
360
+ DECRYPT_ACTION,
361
+ KEY_CREATE_ABILITY,
362
+ KEY_BLOCK_ABILITY,
363
+ KEY_UNBLOCK_ABILITY
364
+ ]);
365
+ var NAMED_KEYS_FEATURE = "encryption-named-keys-v1";
366
+ var EncryptionResourceError = class extends Error {
367
+ constructor(code, message) {
368
+ super(message);
369
+ this.name = "EncryptionResourceError";
370
+ this.code = code;
371
+ }
372
+ };
373
+ function canonicalOwner(ownerDid) {
374
+ if (!ownerDid.startsWith(PKH_EIP155_PREFIX)) return ownerDid;
375
+ const rest = ownerDid.slice(PKH_EIP155_PREFIX.length);
376
+ const colon = rest.indexOf(":");
377
+ const chain = colon === -1 ? "" : rest.slice(0, colon);
378
+ const address = colon === -1 ? "" : rest.slice(colon + 1);
379
+ if (!/^\d+$/.test(chain) || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
380
+ throw new EncryptionResourceError(
381
+ "bad_owner",
382
+ `owner DID is not a valid did:pkh:eip155 (got ${JSON.stringify(ownerDid)})`
383
+ );
384
+ }
385
+ const chainId = BigInt(chain);
386
+ if (chainId === 0n || chainId > MAX_U64) {
387
+ throw new EncryptionResourceError(
388
+ "bad_owner",
389
+ `did:pkh:eip155 chain id must be a positive u64 (got ${JSON.stringify(chain)})`
390
+ );
391
+ }
392
+ const checksummed = (0, import_bootstrap.pkhDid)(address, 1).slice(PKH_EIP155_PREFIX.length + 2);
393
+ return `${PKH_EIP155_PREFIX}${chainId}:${checksummed}`;
394
+ }
395
+ function assertCanonicalOwner(ownerDid) {
396
+ const canonical = canonicalOwner(ownerDid);
397
+ if (canonical !== ownerDid) {
398
+ throw new EncryptionResourceError(
399
+ "non_canonical_owner",
400
+ `owner DID is not canonical (expected ${canonical})`
401
+ );
402
+ }
403
+ }
404
+ function assertPath(path, what) {
405
+ if (path.length === 0) {
406
+ throw new EncryptionResourceError("empty_path", `${what} must not be empty`);
407
+ }
408
+ if (new TextEncoder().encode(path).length > MAX_PATH_BYTES) {
409
+ throw new EncryptionResourceError(
410
+ "path_too_long",
411
+ `${what} exceeds ${MAX_PATH_BYTES} bytes`
412
+ );
413
+ }
414
+ if (!KEY_PATH_RE.test(path)) {
415
+ throw new EncryptionResourceError(
416
+ "bad_path",
417
+ `${what} ${JSON.stringify(path)} must match ${KEY_PATH_RE.source}`
418
+ );
419
+ }
420
+ const segments = path.split("/");
421
+ if (segments[0] === "apps" && segments.length < (what === "key path" ? 3 : 2)) {
422
+ throw new EncryptionResourceError(
423
+ "bad_path",
424
+ `${what} ${JSON.stringify(path)} under apps/ needs ${what === "key path" ? "apps/<id>/<name>" : "apps/<id>/"}`
425
+ );
426
+ }
427
+ }
428
+ function parseEncryptionResource(uri) {
429
+ if (typeof uri !== "string" || !uri.startsWith(ENCRYPTION_NETWORK_URN_PREFIX)) {
430
+ throw new EncryptionResourceError(
431
+ "not_encryption_urn",
432
+ `resource must start with ${ENCRYPTION_NETWORK_URN_PREFIX}`
433
+ );
434
+ }
435
+ const body = uri.slice(ENCRYPTION_NETWORK_URN_PREFIX.length);
436
+ const match = RESOURCE_RE.exec(body);
437
+ if (match === null) {
438
+ throw new EncryptionResourceError(
439
+ "malformed",
440
+ `resource is not <ownerDid>:<network>[/<path>] (got ${JSON.stringify(uri)})`
441
+ );
442
+ }
443
+ const ownerDid = match[1];
444
+ const network = match[2];
445
+ const rawPath = match[3];
446
+ if (rawPath === void 0) {
447
+ const canonicalOwnerDid = canonicalOwner(ownerDid);
448
+ const bareId = `${ENCRYPTION_NETWORK_URN_PREFIX}${canonicalOwnerDid}:${network}`;
449
+ try {
450
+ parseNetworkId(bareId);
451
+ } catch (error) {
452
+ if (error instanceof NetworkIdError) {
453
+ throw new EncryptionResourceError("bad_network", error.message);
454
+ }
455
+ throw error;
456
+ }
457
+ return { kind: "network", ownerDid: canonicalOwnerDid, network, networkId: bareId };
458
+ }
459
+ if (!KEY_NETWORK_RE.test(network)) {
460
+ throw new EncryptionResourceError(
461
+ "bad_network",
462
+ `network ${JSON.stringify(network)} must match ${KEY_NETWORK_RE.source}`
463
+ );
464
+ }
465
+ assertCanonicalOwner(ownerDid);
466
+ const networkId = `${ENCRYPTION_NETWORK_URN_PREFIX}${ownerDid}:${network}`;
467
+ if (rawPath.endsWith("/")) {
468
+ const prefix = rawPath.slice(0, -1);
469
+ assertPath(prefix, "prefix");
470
+ return { kind: "prefix", ownerDid, network, networkId, prefix };
471
+ }
472
+ assertPath(rawPath, "key path");
473
+ return { kind: "key", ownerDid, network, networkId, path: rawPath };
474
+ }
475
+ function canonicalNetworkId(networkId) {
476
+ let parsed;
477
+ try {
478
+ parsed = parseNetworkId(networkId);
479
+ } catch (error) {
480
+ if (error instanceof NetworkIdError) {
481
+ throw new EncryptionResourceError("bad_network", error.message);
482
+ }
483
+ throw error;
484
+ }
485
+ assertCanonicalOwner(parsed.ownerDid);
486
+ return parsed.networkId;
487
+ }
488
+ function parseBuilt(resource, networkId) {
489
+ if (parseEncryptionResource(resource).networkId !== networkId) {
490
+ throw new EncryptionResourceError(
491
+ "malformed",
492
+ `built resource does not round-trip (got ${JSON.stringify(resource)})`
493
+ );
494
+ }
495
+ }
496
+ function formatKeyResource(networkId, path) {
497
+ const id = canonicalNetworkId(networkId);
498
+ assertPath(path, "key path");
499
+ const resource = `${id}/${path}`;
500
+ parseBuilt(resource, id);
501
+ return resource;
502
+ }
503
+ function nodeSupportsNamedKeys(info) {
504
+ const features = info?.features;
505
+ return Array.isArray(features) && features.includes(NAMED_KEYS_FEATURE);
506
+ }
507
+
508
+ // src/encryption/namedKeys.ts
509
+ var KEY_ADMIN_FACT_TYPE = "tinycloud.encryption.key-admin/v1";
510
+ var KEY_DESCRIBE_FACT_TYPE = "tinycloud.encryption.key-describe/v1";
511
+ var NAMED_KEY_ENVELOPE_VERSION = 2;
512
+ var MAX_ECHO_CHARS = 120;
513
+ function escapeNonPrintable(value) {
514
+ let out = "";
515
+ for (const ch of value) {
516
+ const cp = ch.codePointAt(0);
517
+ out += cp >= 32 && cp <= 126 ? ch : `\\u{${cp.toString(16).toUpperCase().padStart(4, "0")}}`;
518
+ }
519
+ return out;
520
+ }
521
+ function safeEcho(value) {
522
+ const text = typeof value === "string" ? value : String(value);
523
+ const chars = Array.from(text);
524
+ const clipped = chars.length > MAX_ECHO_CHARS ? `${chars.slice(0, MAX_ECHO_CHARS).join("")}...` : text;
525
+ return escapeNonPrintable(clipped);
526
+ }
527
+ var NamedKeyError = class extends Error {
528
+ constructor(code, message, init = {}) {
529
+ super(escapeNonPrintable(message), init.cause === void 0 ? void 0 : { cause: init.cause });
530
+ this.name = "NamedKeyError";
531
+ this.code = code;
532
+ if (init.status !== void 0) this.status = init.status;
533
+ if (init.nodeCode !== void 0) this.nodeCode = init.nodeCode;
534
+ if (init.detail !== void 0) this.detail = init.detail;
535
+ if (init.rawState !== void 0) this.rawState = init.rawState;
536
+ if (init.kind !== void 0) this.kind = init.kind;
537
+ if (init.input !== void 0) this.input = init.input;
538
+ }
539
+ };
540
+ var NODE_CODE_MAP = Object.assign(
541
+ /* @__PURE__ */ Object.create(null),
542
+ {
543
+ key_blocked: "KEY_BLOCKED",
544
+ key_not_found: "KEY_NOT_FOUND",
545
+ key_exists: "KEY_EXISTS",
546
+ network_not_found: "NETWORK_NOT_FOUND",
547
+ network_not_active: "NETWORK_NOT_ACTIVE",
548
+ key_limit_exceeded: "KEY_LIMIT_EXCEEDED",
549
+ deletable_unavailable: "DELETABLE_UNAVAILABLE",
550
+ key_not_deletable: "KEY_NOT_DELETABLE",
551
+ key_path_invalid: "KEY_PATH_INVALID",
552
+ key_unwrap_failed: "KEY_UNWRAP_FAILED",
553
+ receiver_key_invalid: "RECEIVER_KEY_INVALID"
554
+ }
555
+ );
556
+ var UNAVAILABLE_NODE_CODES = Object.assign(
557
+ /* @__PURE__ */ Object.create(null),
558
+ { key_deleted: true }
559
+ );
560
+ var NODE_ERROR_RE = /^([a-z][a-z0-9_]*):\s?([\s\S]*)$/;
561
+ var MAX_NODE_CODE_CHARS = 64;
562
+ function mapNodeKeyError(status, text, context) {
563
+ const match = NODE_ERROR_RE.exec(text.trim());
564
+ const nodeCode = match?.[1]?.slice(0, MAX_NODE_CODE_CHARS);
565
+ const detail = match?.[2]?.trim();
566
+ const base = {
567
+ status,
568
+ ...nodeCode === void 0 ? {} : { nodeCode },
569
+ ...detail === void 0 || detail.length === 0 ? {} : { detail }
570
+ };
571
+ if (nodeCode !== void 0) {
572
+ const mapped = NODE_CODE_MAP[nodeCode];
573
+ if (mapped !== void 0) {
574
+ return new NamedKeyError(mapped, `${context}: ${nodeCode}`, base);
575
+ }
576
+ if (UNAVAILABLE_NODE_CODES[nodeCode] === true) {
577
+ return new NamedKeyError("KEY_UNAVAILABLE", `${context}: key unavailable (${nodeCode})`, {
578
+ ...base,
579
+ rawState: nodeCode
580
+ });
581
+ }
582
+ }
583
+ return new NamedKeyError("NODE_ERROR", `${context}: node responded ${status}`, base);
584
+ }
585
+ function requireKeyTarget(resource) {
586
+ let parsed;
587
+ try {
588
+ parsed = parseEncryptionResource(resource);
589
+ } catch (error) {
590
+ throw new NamedKeyError(
591
+ "INVALID_KEY_TARGET",
592
+ `not a valid encryption key resource: ${safeEcho(error instanceof Error ? error.message : error)}`,
593
+ { cause: error, input: resource }
594
+ );
595
+ }
596
+ if (parsed.kind !== "key") {
597
+ throw new NamedKeyError(
598
+ "INVALID_KEY_TARGET",
599
+ `a ${parsed.kind} resource cannot be an invocation target; an exact key is required`,
600
+ { input: resource }
601
+ );
602
+ }
603
+ return parsed;
604
+ }
605
+ function buildKeyAdminFacts(input) {
606
+ const key = requireKeyTarget(input.keyResource);
607
+ return {
608
+ type: KEY_ADMIN_FACT_TYPE,
609
+ targetNode: input.targetNode,
610
+ networkId: key.networkId,
611
+ keyPath: key.path,
612
+ action: input.action,
613
+ bodyHash: canonicalHashHex(input.crypto.sha256, input.body)
614
+ };
615
+ }
616
+ function buildKeyDescribeFacts(input) {
617
+ const key = requireKeyTarget(input.keyResource);
618
+ return {
619
+ type: KEY_DESCRIBE_FACT_TYPE,
620
+ targetNode: input.targetNode,
621
+ networkId: key.networkId,
622
+ keyPath: key.path
623
+ };
624
+ }
625
+ var DESCRIBE_ABILITIES = {
626
+ [DECRYPT_ACTION]: true,
627
+ [KEY_CREATE_ABILITY]: true,
628
+ [KEY_BLOCK_ABILITY]: true,
629
+ [KEY_UNBLOCK_ABILITY]: true
630
+ };
631
+ function invalidResponse(what) {
632
+ return new NamedKeyError("INVALID_RESPONSE", `node returned an invalid ${what}`);
633
+ }
634
+ function parsePublicDescriptor(raw, networkId, keyPath) {
635
+ const value = typeof raw === "object" && raw !== null ? raw : {};
636
+ if (typeof value.alg !== "string" || typeof value.keyVersion !== "number" || !Number.isInteger(value.keyVersion) || typeof value.publicEncryptionKey !== "string" || value.publicEncryptionKey.length === 0) {
637
+ throw invalidResponse("key descriptor");
638
+ }
639
+ if (value.networkId !== networkId || value.keyPath !== keyPath) {
640
+ throw new NamedKeyError(
641
+ "INVALID_RESPONSE",
642
+ "node returned a descriptor for a different network or key"
643
+ );
644
+ }
645
+ return {
646
+ networkId,
647
+ keyPath,
648
+ alg: value.alg,
649
+ keyVersion: value.keyVersion,
650
+ publicEncryptionKey: value.publicEncryptionKey
651
+ };
652
+ }
653
+ function parseFullDescriptor(value, networkId, keyPath) {
654
+ const base = parsePublicDescriptor(value, networkId, keyPath);
655
+ const record = value;
656
+ if (typeof record.state !== "string" || typeof record.kind !== "string") {
657
+ throw invalidResponse("key descriptor (state/kind missing)");
658
+ }
659
+ return {
660
+ ...base,
661
+ state: record.state,
662
+ kind: record.kind,
663
+ ...typeof record.createdAt === "string" ? { createdAt: record.createdAt } : {},
664
+ ...typeof record.updatedAt === "string" ? { updatedAt: record.updatedAt } : {}
665
+ };
666
+ }
667
+ function assertEnsuredDescriptor(descriptor, expectedKind) {
668
+ if (descriptor.state === "blocked") {
669
+ throw new NamedKeyError("KEY_BLOCKED", `key ${safeEcho(descriptor.keyPath)} is blocked`, {
670
+ rawState: descriptor.state
671
+ });
672
+ }
673
+ if (descriptor.state !== "active") {
674
+ throw new NamedKeyError(
675
+ "KEY_UNAVAILABLE",
676
+ `key ${safeEcho(descriptor.keyPath)} is unavailable (state ${safeEcho(descriptor.state)})`,
677
+ { rawState: descriptor.state }
678
+ );
679
+ }
680
+ if (descriptor.kind !== expectedKind) {
681
+ throw new NamedKeyError(
682
+ "KEY_KIND_MISMATCH",
683
+ `key ${safeEcho(descriptor.keyPath)} exists with kind ${safeEcho(descriptor.kind)}, not ${expectedKind}`,
684
+ { kind: descriptor.kind }
685
+ );
686
+ }
687
+ return descriptor;
688
+ }
689
+ function isAppKeyPath(path) {
690
+ return path.startsWith("apps/");
691
+ }
692
+ var REQUIRED_ENVELOPE_STRINGS = [
693
+ "alg",
694
+ "encryptedSymmetricKey",
695
+ "encryptedSymmetricKeyHash",
696
+ "ciphertext"
697
+ ];
698
+ var NamedKeyClient = class {
699
+ constructor(config) {
700
+ this.config = config;
701
+ }
702
+ networkFor(options) {
703
+ return options?.networkId ?? this.config.defaultNetworkId();
704
+ }
705
+ keyUrn(networkId, path) {
706
+ try {
707
+ return formatKeyResource(networkId, path);
708
+ } catch (error) {
709
+ throw new NamedKeyError(
710
+ "INVALID_KEY_TARGET",
711
+ safeEcho(error instanceof Error ? error.message : error),
712
+ { cause: error, input: `${networkId}/${path}` }
713
+ );
714
+ }
715
+ }
716
+ /**
717
+ * Require the named-keys feature and return the node DID. The first call
718
+ * reads `/info`; a positive answer is cached for the life of this client, so
719
+ * later calls make no `/info` request. A node without the feature is NOT
720
+ * cached: the typed `NAMED_KEYS_UNSUPPORTED` error is thrown and the next
721
+ * call re-reads (so a retry sees an upgraded node). No key request is made
722
+ * before the gate passes.
723
+ */
724
+ requireNode() {
725
+ if (this.nodeId === void 0) {
726
+ const pending = this.readNodeInfo();
727
+ this.nodeId = pending;
728
+ pending.catch(() => {
729
+ if (this.nodeId === pending) this.nodeId = void 0;
730
+ });
731
+ }
732
+ return this.nodeId;
733
+ }
734
+ /** Clear the cached `/info` answer; the next call reads it again. */
735
+ refreshNodeInfo() {
736
+ this.nodeId = void 0;
737
+ }
738
+ async readNodeInfo() {
739
+ const response = await this.config.fetch(`${this.config.host}/info`);
740
+ if (!response.ok) {
741
+ throw mapNodeKeyError(response.status, await safeText(response), "failed to read node info");
742
+ }
743
+ const info = await response.json();
744
+ if (!nodeSupportsNamedKeys(info)) {
745
+ throw new NamedKeyError(
746
+ "NAMED_KEYS_UNSUPPORTED",
747
+ "this node does not support named encryption keys"
748
+ );
749
+ }
750
+ if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
751
+ throw invalidResponse("/info response (nodeId missing)");
752
+ }
753
+ return info.nodeId;
754
+ }
755
+ keysUrl(networkId, path) {
756
+ return `${this.config.host}/encryption/networks/${networkId}/keys/${path}`;
757
+ }
758
+ /** Public GET: the key's public key. Derive-only (no 404) outside `apps/`; under `apps/` the node answers 404. */
759
+ async getPublicKey(path, options) {
760
+ const networkId = this.networkFor(options);
761
+ this.keyUrn(networkId, path);
762
+ await this.requireNode();
763
+ const response = await this.config.fetch(this.keysUrl(networkId, path), { method: "GET" });
764
+ if (!response.ok) {
765
+ throw mapNodeKeyError(response.status, await safeText(response), "failed to read key");
766
+ }
767
+ return parsePublicDescriptor(await response.json(), networkId, path);
768
+ }
769
+ /**
770
+ * Authenticated GET: the full descriptor, or `null` on `key_not_found`.
771
+ * `ability` is the one capability the invocation carries (default decrypt).
772
+ */
773
+ async describeKey(path, options) {
774
+ const networkId = this.networkFor(options);
775
+ const body = await this.describeBody(path, options);
776
+ return body === null ? null : parseFullDescriptor(body, networkId, path);
777
+ }
778
+ /** Public fields of an authenticated describe (decrypt ability); `key_not_found` throws. */
779
+ async describePublic(path, options) {
780
+ const networkId = this.networkFor(options);
781
+ const body = await this.describeBody(path, options);
782
+ if (body === null) {
783
+ throw new NamedKeyError("KEY_NOT_FOUND", `key ${safeEcho(path)} does not exist`, { status: 404 });
784
+ }
785
+ return parsePublicDescriptor(body, networkId, path);
786
+ }
787
+ /** The authenticated GET response body, or `null` on `key_not_found`. */
788
+ async describeBody(path, options) {
789
+ const networkId = this.networkFor(options);
790
+ const keyResource = this.keyUrn(networkId, path);
791
+ const ability = options?.ability ?? DECRYPT_ACTION;
792
+ if (DESCRIBE_ABILITIES[ability] !== true) {
793
+ throw new NamedKeyError("INVALID_KEY_TARGET", `${safeEcho(ability)} cannot describe a key`, { input: ability });
794
+ }
795
+ const targetNode = await this.requireNode();
796
+ const signed = await this.config.signInvocation({
797
+ targetNode,
798
+ resource: keyResource,
799
+ action: ability,
800
+ facts: buildKeyDescribeFacts({ targetNode, keyResource })
801
+ });
802
+ const response = await this.config.fetch(this.keysUrl(networkId, path), {
803
+ method: "GET",
804
+ headers: { Authorization: signed.authorization }
805
+ });
806
+ if (response.status === 404) {
807
+ const error = mapNodeKeyError(404, await safeText(response), "failed to describe key");
808
+ if (error.code === "KEY_NOT_FOUND") return null;
809
+ throw error;
810
+ }
811
+ if (!response.ok) {
812
+ throw mapNodeKeyError(response.status, await safeText(response), "failed to describe key");
813
+ }
814
+ return response.json();
815
+ }
816
+ async admin(route, action, path, options) {
817
+ const networkId = this.networkFor(options);
818
+ const keyResource = this.keyUrn(networkId, path);
819
+ const body = route === "keys" ? { keyPath: path, deletable: isAppKeyPath(path) } : { keyPath: path };
820
+ const targetNode = await this.requireNode();
821
+ const signed = await this.config.signInvocation({
822
+ targetNode,
823
+ resource: keyResource,
824
+ action,
825
+ facts: buildKeyAdminFacts({
826
+ crypto: this.config.crypto,
827
+ targetNode,
828
+ keyResource,
829
+ action,
830
+ body
831
+ })
832
+ });
833
+ const response = await this.config.fetch(
834
+ `${this.config.host}/encryption/networks/${networkId}/${route}`,
835
+ {
836
+ method: "POST",
837
+ headers: {
838
+ Authorization: signed.authorization,
839
+ "Content-Type": "application/json"
840
+ },
841
+ body: canonicalize(body)
842
+ }
843
+ );
844
+ if (!response.ok) {
845
+ throw mapNodeKeyError(response.status, await safeText(response), `failed to ${action}`);
846
+ }
847
+ return parseFullDescriptor(unwrapDescriptor(await response.json()), networkId, path);
848
+ }
849
+ /** `key.create`: `deletable` is `true` exactly under `apps/`, else a deterministic key. */
850
+ createKey(path, options) {
851
+ return this.admin("keys", KEY_CREATE_ABILITY, path, options);
852
+ }
853
+ blockKey(path, options) {
854
+ return this.admin("key-actions/block", KEY_BLOCK_ABILITY, path, options);
855
+ }
856
+ unblockKey(path, options) {
857
+ return this.admin("key-actions/unblock", KEY_UNBLOCK_ABILITY, path, options);
858
+ }
859
+ /**
860
+ * Make sure a key exists and is usable: authenticated GET; on
861
+ * `key_not_found` create it; on 409 `key_exists` read it again. Succeeds only
862
+ * for `state: "active"` and the kind the path implies: `deletable` under
863
+ * `apps/`, `deterministic` elsewhere.
864
+ */
865
+ async ensureKey(path, options) {
866
+ const describe = () => this.describeKey(path, { ...options, ability: KEY_CREATE_ABILITY });
867
+ let descriptor = await describe();
868
+ if (descriptor === null) {
869
+ try {
870
+ descriptor = await this.createKey(path, options);
871
+ } catch (error) {
872
+ if (!(error instanceof NamedKeyError) || error.code !== "KEY_EXISTS") throw error;
873
+ descriptor = await describe();
874
+ if (descriptor === null) {
875
+ throw new NamedKeyError(
876
+ "KEY_NOT_FOUND",
877
+ `key ${safeEcho(path)} reported as existing but could not be read back`
878
+ );
879
+ }
880
+ }
881
+ }
882
+ return assertEnsuredDescriptor(descriptor, isAppKeyPath(path) ? "deletable" : "deterministic");
883
+ }
884
+ /** Encrypt to a named key: envelope v2, symmetric key wrapped to the key's public key. */
885
+ async encrypt(path, plaintext, options) {
886
+ const descriptor = isAppKeyPath(path) ? await this.describePublic(path, options) : await this.getPublicKey(path, options);
887
+ const { crypto } = this.config;
888
+ const symmetricKey = crypto.randomBytes(32);
889
+ const ciphertext = crypto.authEncrypt(symmetricKey, plaintext, options?.aad);
890
+ const wrapped = crypto.sealToNetworkKey(
891
+ base64Decode(descriptor.publicEncryptionKey),
892
+ symmetricKey
893
+ );
894
+ const encryptedSymmetricKey = base64Encode(wrapped);
895
+ return {
896
+ v: NAMED_KEY_ENVELOPE_VERSION,
897
+ networkId: descriptor.networkId,
898
+ keyPath: descriptor.keyPath,
899
+ alg: descriptor.alg ?? DEFAULT_ENCRYPTION_ALG,
900
+ keyVersion: descriptor.keyVersion,
901
+ encryptedSymmetricKey,
902
+ encryptedSymmetricKeyHash: canonicalHashHex(crypto.sha256, encryptedSymmetricKey),
903
+ ciphertext: base64Encode(ciphertext),
904
+ ...options?.aad !== void 0 ? { aad: base64Encode(options.aad) } : {},
905
+ ...options?.metadata !== void 0 ? { metadata: options.metadata } : {}
906
+ };
907
+ }
908
+ /**
909
+ * Decrypt one envelope. v1 (no `keyPath`) uses the root-key path exactly as
910
+ * before; v2 uses one decrypt capability on the exact key via `/invoke`.
911
+ */
912
+ async decrypt(envelope) {
913
+ const candidate = typeof envelope === "object" && envelope !== null ? envelope : {};
914
+ if (candidate.v === 1 && candidate.keyPath === void 0) {
915
+ return this.decryptRoot(candidate);
916
+ }
917
+ if (candidate.v === NAMED_KEY_ENVELOPE_VERSION && typeof candidate.keyPath === "string") {
918
+ return this.decryptKey(this.validateKeyEnvelope(candidate));
919
+ }
920
+ throw new NamedKeyError(
921
+ "INVALID_ENVELOPE",
922
+ "envelope must be v1 without keyPath or v2 with keyPath"
923
+ );
924
+ }
925
+ /**
926
+ * Decrypt a batch. Each envelope is its own invocation with its own single
927
+ * capability, so v1 and v2 (or two different keys) are never combined.
928
+ */
929
+ async decryptMany(envelopes) {
930
+ const results = [];
931
+ for (const envelope of envelopes) {
932
+ try {
933
+ results.push({ ok: true, data: await this.decrypt(envelope) });
934
+ } catch (error) {
935
+ results.push({
936
+ ok: false,
937
+ error: error instanceof NamedKeyError ? error : new NamedKeyError("DECRYPT_FAILED", "decrypt failed", { cause: error })
938
+ });
939
+ }
940
+ }
941
+ return results;
942
+ }
943
+ async decryptRoot(envelope) {
944
+ if (this.config.decryptRoot === void 0) {
945
+ throw new NamedKeyError("DECRYPT_FAILED", "no root-key decrypt path is configured");
946
+ }
947
+ const result = await this.config.decryptRoot(envelope);
948
+ if (!result.ok) {
949
+ throw new NamedKeyError("DECRYPT_FAILED", safeEcho(result.error.message), { cause: result.error });
950
+ }
951
+ return result.data;
952
+ }
953
+ validateKeyEnvelope(e) {
954
+ for (const field of REQUIRED_ENVELOPE_STRINGS) {
955
+ if (typeof e[field] !== "string" || e[field].length === 0) {
956
+ throw new NamedKeyError("INVALID_ENVELOPE", `envelope.${field} must be a non-empty string`);
957
+ }
958
+ }
959
+ if (typeof e.keyVersion !== "number" || !Number.isInteger(e.keyVersion)) {
960
+ throw new NamedKeyError("INVALID_ENVELOPE", "envelope.keyVersion must be an integer");
961
+ }
962
+ if (typeof e.networkId !== "string") {
963
+ throw new NamedKeyError("INVALID_ENVELOPE", "envelope.networkId must be a string");
964
+ }
965
+ try {
966
+ parseNetworkId(e.networkId);
967
+ } catch (error) {
968
+ throw new NamedKeyError("INVALID_ENVELOPE", "envelope.networkId is malformed", { cause: error });
969
+ }
970
+ const expectedHash = canonicalHashHex(
971
+ this.config.crypto.sha256,
972
+ e.encryptedSymmetricKey
973
+ );
974
+ if (expectedHash !== e.encryptedSymmetricKeyHash) {
975
+ throw new NamedKeyError(
976
+ "INVALID_ENVELOPE",
977
+ "envelope.encryptedSymmetricKeyHash does not match encryptedSymmetricKey"
978
+ );
979
+ }
980
+ return e;
981
+ }
982
+ async decryptKey(envelope) {
983
+ const { crypto } = this.config;
984
+ const keyResource = this.keyUrn(envelope.networkId, envelope.keyPath);
985
+ const targetNode = await this.requireNode();
986
+ const receiverKey = generateRandomReceiverKey({ crypto });
987
+ const receiverPublicKey = base64Encode(receiverKey.publicKey);
988
+ const receiverPublicKeyHash = canonicalHashHex(crypto.sha256, receiverPublicKey);
989
+ const body = {
990
+ type: DECRYPT_FACT_TYPE,
991
+ targetNode,
992
+ networkId: envelope.networkId,
993
+ keyPath: envelope.keyPath,
994
+ alg: envelope.alg,
995
+ keyVersion: envelope.keyVersion,
996
+ encryptedSymmetricKey: envelope.encryptedSymmetricKey,
997
+ encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,
998
+ receiverPublicKey,
999
+ receiverPublicKeyHash
1000
+ };
1001
+ const canonicalBody = canonicalize(body);
1002
+ const facts = {
1003
+ type: DECRYPT_FACT_TYPE,
1004
+ targetNode,
1005
+ networkId: envelope.networkId,
1006
+ keyPath: envelope.keyPath,
1007
+ bodyHash: canonicalHashHex(crypto.sha256, body),
1008
+ encryptedSymmetricKeyHash: envelope.encryptedSymmetricKeyHash,
1009
+ receiverPublicKeyHash,
1010
+ alg: envelope.alg,
1011
+ keyVersion: envelope.keyVersion
1012
+ };
1013
+ const signed = await this.config.signInvocation({
1014
+ targetNode,
1015
+ resource: keyResource,
1016
+ action: DECRYPT_ACTION,
1017
+ facts
1018
+ });
1019
+ const response = await this.config.fetch(`${this.config.host}/invoke`, {
1020
+ method: "POST",
1021
+ headers: {
1022
+ Authorization: signed.authorization,
1023
+ "Content-Type": "application/json"
1024
+ },
1025
+ body: canonicalBody
1026
+ });
1027
+ if (!response.ok) {
1028
+ throw mapNodeKeyError(response.status, await safeText(response), "failed to decrypt");
1029
+ }
1030
+ const verified = verifyDecryptResponse({
1031
+ crypto,
1032
+ request: body,
1033
+ facts,
1034
+ invocationCid: signed.invocationCid,
1035
+ requestBodyHash: facts.bodyHash,
1036
+ response: await response.json()
1037
+ });
1038
+ if (!verified.ok) {
1039
+ throw new NamedKeyError("INVALID_RESPONSE", safeEcho(verified.error.message), { cause: verified.error });
1040
+ }
1041
+ const symmetricKey = openWrappedKey(crypto, receiverKey.privateKey, verified.data);
1042
+ try {
1043
+ return crypto.authDecrypt(
1044
+ symmetricKey,
1045
+ base64Decode(envelope.ciphertext),
1046
+ envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0
1047
+ );
1048
+ } catch (error) {
1049
+ throw new NamedKeyError("DECRYPT_FAILED", "local decryption failed", { cause: error });
1050
+ }
1051
+ }
1052
+ };
1053
+ function unwrapDescriptor(body) {
1054
+ const outer = typeof body === "object" && body !== null ? body : {};
1055
+ return typeof outer.descriptor === "object" && outer.descriptor !== null ? outer.descriptor : body;
1056
+ }
1057
+ async function safeText(response) {
1058
+ try {
1059
+ return await response.text();
1060
+ } catch {
1061
+ return "";
1062
+ }
1063
+ }
1064
+ // Annotate the CommonJS export names for ESM import in node:
1065
+ 0 && (module.exports = {
1066
+ KEY_ADMIN_FACT_TYPE,
1067
+ KEY_DESCRIBE_FACT_TYPE,
1068
+ NAMED_KEY_ENVELOPE_VERSION,
1069
+ NamedKeyClient,
1070
+ NamedKeyError,
1071
+ buildKeyAdminFacts,
1072
+ buildKeyDescribeFacts,
1073
+ mapNodeKeyError,
1074
+ requireKeyTarget,
1075
+ safeEcho
1076
+ });
1077
+ //# sourceMappingURL=named-keys.cjs.map