@tinycloud/node-sdk 2.2.0-beta.8 → 2.2.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/core.js CHANGED
@@ -205,10 +205,12 @@ import {
205
205
  checkNodeInfo,
206
206
  AutoApproveSpaceCreationHandler,
207
207
  DEFAULT_MANIFEST_SPACE,
208
+ ENCRYPTION_PERMISSION_SERVICE,
208
209
  composeManifestRequest,
209
210
  resourceCapabilitiesToAbilitiesMap,
210
211
  resourceCapabilitiesToSpaceAbilitiesMap,
211
- resolveTinyCloudHosts
212
+ resolveTinyCloudHosts,
213
+ EXPIRY
212
214
  } from "@tinycloud/sdk-core";
213
215
 
214
216
  // src/authorization/strategies.ts
@@ -268,7 +270,7 @@ var NodeUserAuthorization = class {
268
270
  ]
269
271
  }
270
272
  };
271
- this.sessionExpirationMs = config.sessionExpirationMs ?? 60 * 60 * 1e3;
273
+ this.sessionExpirationMs = config.sessionExpirationMs ?? EXPIRY.SESSION_MS;
272
274
  this.autoCreateSpace = config.autoCreateSpace ?? false;
273
275
  this.spaceCreationHandler = config.spaceCreationHandler;
274
276
  this.tinycloudHosts = config.tinycloudHosts;
@@ -321,6 +323,23 @@ var NodeUserAuthorization = class {
321
323
  get tinyCloudSession() {
322
324
  return this._tinyCloudSession;
323
325
  }
326
+ /**
327
+ * Rehydrate the auth-layer session from previously-persisted delegation
328
+ * data. Used by {@link TinyCloudNode.restoreSession} so that downstream
329
+ * surfaces that read from `tinyCloudSession` (notably
330
+ * `grantRuntimePermissions`, which extracts the SIWE expiry from it) work
331
+ * without re-running the full sign-in flow.
332
+ *
333
+ * Caller must supply the same fields that `signIn` would have written —
334
+ * `siwe` is the load-bearing one because `extractSiweExpiration` returns
335
+ * undefined for missing SIWEs and the SDK then treats the session as
336
+ * expired-at-epoch-zero.
337
+ */
338
+ setRestoredTinyCloudSession(session) {
339
+ this._tinyCloudSession = session;
340
+ this._address = session.address;
341
+ this._chainId = session.chainId;
342
+ }
324
343
  async resolveTinyCloudHostsForSignIn(address, chainId) {
325
344
  if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
326
345
  return;
@@ -391,21 +410,64 @@ var NodeUserAuthorization = class {
391
410
  }
392
411
  return this.wasm.makeSpaceId(address, chainId, space);
393
412
  }
413
+ defaultEncryptionNetworkId(address, chainId) {
414
+ return `urn:tinycloud:encryption:did:pkh:eip155:${chainId}:${address}:default`;
415
+ }
394
416
  resolveSignInCapabilities(address, chainId) {
395
417
  const request = this.getCapabilityRequest();
396
418
  if (request === void 0) {
419
+ const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
420
+ const secretsSpaceId = this.wasm.makeSpaceId(address, chainId, "secrets");
397
421
  return {
398
422
  abilities: this.defaultActions,
399
- spaceId: this.wasm.makeSpaceId(address, chainId, this.spacePrefix)
423
+ spaceId: this.wasm.makeSpaceId(address, chainId, this.spacePrefix),
424
+ spaceAbilities: {
425
+ [secretsSpaceId]: {
426
+ kv: {
427
+ "vault/secrets/": [
428
+ "tinycloud.kv/get",
429
+ "tinycloud.kv/put",
430
+ "tinycloud.kv/del",
431
+ "tinycloud.kv/list",
432
+ "tinycloud.kv/metadata"
433
+ ]
434
+ }
435
+ }
436
+ },
437
+ rawAbilities: {
438
+ [defaultNetworkId]: [
439
+ "tinycloud.encryption/decrypt",
440
+ "tinycloud.encryption/network.create"
441
+ ]
442
+ }
400
443
  };
401
444
  }
402
- const primarySpaceName = request.resources.find((entry) => entry.space !== "account")?.space ?? DEFAULT_MANIFEST_SPACE;
445
+ const rawAbilities = {};
446
+ const spaceResources = request.resources.filter((entry) => {
447
+ if (entry.service !== ENCRYPTION_PERMISSION_SERVICE) {
448
+ return true;
449
+ }
450
+ const existing = rawAbilities[entry.path];
451
+ if (existing === void 0) {
452
+ rawAbilities[entry.path] = [...entry.actions];
453
+ } else {
454
+ const seen = new Set(existing);
455
+ for (const action of entry.actions) {
456
+ if (!seen.has(action)) {
457
+ existing.push(action);
458
+ seen.add(action);
459
+ }
460
+ }
461
+ }
462
+ return false;
463
+ });
464
+ const primarySpaceName = spaceResources.find((entry) => entry.space !== "account")?.space ?? DEFAULT_MANIFEST_SPACE;
403
465
  const primarySpaceId = this.resolveSpaceName(
404
466
  primarySpaceName,
405
467
  address,
406
468
  chainId
407
469
  );
408
- const bySpace = resourceCapabilitiesToSpaceAbilitiesMap(request.resources);
470
+ const bySpace = resourceCapabilitiesToSpaceAbilitiesMap(spaceResources);
409
471
  const spaceAbilities = {};
410
472
  for (const [space, abilities] of Object.entries(bySpace)) {
411
473
  spaceAbilities[this.resolveSpaceName(space, address, chainId)] = abilities;
@@ -413,7 +475,8 @@ var NodeUserAuthorization = class {
413
475
  return {
414
476
  abilities: spaceAbilities[primarySpaceId] ?? resourceCapabilitiesToAbilitiesMap([]),
415
477
  spaceId: primarySpaceId,
416
- spaceAbilities
478
+ spaceAbilities,
479
+ rawAbilities: Object.keys(rawAbilities).length > 0 ? rawAbilities : void 0
417
480
  };
418
481
  }
419
482
  /**
@@ -638,6 +701,7 @@ var NodeUserAuthorization = class {
638
701
  const prepared = this.wasm.prepareSession({
639
702
  abilities: capabilityPlan.abilities,
640
703
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
704
+ ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
641
705
  address,
642
706
  chainId,
643
707
  domain: this.domain,
@@ -783,6 +847,7 @@ var NodeUserAuthorization = class {
783
847
  const prepared = this.wasm.prepareSession({
784
848
  abilities: capabilityPlan.abilities,
785
849
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
850
+ ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
786
851
  address,
787
852
  chainId,
788
853
  domain: this.domain,
@@ -959,6 +1024,7 @@ import {
959
1024
  DuckDbService as DuckDbService2,
960
1025
  HooksService as HooksService2,
961
1026
  DataVaultService,
1027
+ EncryptionService,
962
1028
  SecretsService,
963
1029
  createVaultCrypto,
964
1030
  ServiceContext as ServiceContext2,
@@ -970,12 +1036,17 @@ import {
970
1036
  UnsupportedFeatureError,
971
1037
  makePublicSpaceId,
972
1038
  ACCOUNT_REGISTRY_SPACE,
1039
+ ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
973
1040
  PermissionNotInManifestError,
974
1041
  SessionExpiredError,
975
- expandActionShortNames,
1042
+ expandPermissionEntries as expandPermissionEntriesCore,
976
1043
  isCapabilitySubset,
977
1044
  parseRecapCapabilities,
978
- SERVICE_LONG_TO_SHORT
1045
+ SERVICE_LONG_TO_SHORT,
1046
+ EXPIRY as EXPIRY3,
1047
+ canonicalHashHex,
1048
+ canonicalizeEncryptionJson,
1049
+ verifyDidKeyEd25519Signature
979
1050
  } from "@tinycloud/sdk-core";
980
1051
 
981
1052
  // src/DelegatedAccess.ts
@@ -1158,7 +1229,8 @@ function createWasmKeyProvider(sessionManager) {
1158
1229
  // src/delegateToHelpers.ts
1159
1230
  import {
1160
1231
  parseExpiry,
1161
- SiweMessage
1232
+ SiweMessage,
1233
+ EXPIRY as EXPIRY2
1162
1234
  } from "@tinycloud/sdk-core";
1163
1235
  function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
1164
1236
  const byService = /* @__PURE__ */ new Map();
@@ -1190,9 +1262,10 @@ function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
1190
1262
  }
1191
1263
  return entries;
1192
1264
  }
1265
+ var DEFAULT_DELEGATION_EXPIRY_MS = EXPIRY2.SESSION_MS;
1193
1266
  function resolveExpiryMs(expiry) {
1194
1267
  if (expiry === void 0) {
1195
- return 60 * 60 * 1e3;
1268
+ return DEFAULT_DELEGATION_EXPIRY_MS;
1196
1269
  }
1197
1270
  if (typeof expiry === "number") {
1198
1271
  if (!Number.isFinite(expiry) || expiry <= 0) {
@@ -1221,10 +1294,11 @@ function extractSiweExpiration(siwe) {
1221
1294
  // src/NodeSecretsService.ts
1222
1295
  import {
1223
1296
  ErrorCodes,
1297
+ resolveSecretListPrefix,
1298
+ resolveSecretPath,
1299
+ expandPermissionEntries,
1224
1300
  resolveManifest
1225
1301
  } from "@tinycloud/sdk-core";
1226
- var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
1227
- var SECRET_PREFIX = "secrets/";
1228
1302
  var SECRETS_SPACE = "secrets";
1229
1303
  function ok() {
1230
1304
  return { ok: true, data: void 0 };
@@ -1240,32 +1314,40 @@ function secretsError(code, message, cause) {
1240
1314
  }
1241
1315
  };
1242
1316
  }
1243
- function actionUrn(action) {
1244
- return `tinycloud.kv/${action}`;
1317
+ function displayActionUrn(action) {
1318
+ switch (action) {
1319
+ case "get":
1320
+ return "tinycloud.kv/get";
1321
+ case "put":
1322
+ return "tinycloud.kv/put";
1323
+ case "del":
1324
+ return "tinycloud.kv/del";
1325
+ case "list":
1326
+ return "tinycloud.kv/list";
1327
+ }
1245
1328
  }
1246
- function secretResourcePath(base, name) {
1247
- return `${base}/${SECRET_PREFIX}${name}`;
1329
+ function secretActionName(action) {
1330
+ return action;
1248
1331
  }
1249
- function secretPermissionEntries(name, action) {
1250
- return [
1251
- {
1252
- service: "tinycloud.kv",
1253
- space: SECRETS_SPACE,
1254
- path: secretResourcePath("keys", name),
1255
- actions: [action],
1256
- skipPrefix: true
1257
- },
1258
- {
1259
- service: "tinycloud.kv",
1260
- space: SECRETS_SPACE,
1261
- path: secretResourcePath("vault", name),
1262
- actions: [action],
1332
+ function secretPermissionEntries(name, options, action, encryptionNetworkId) {
1333
+ const entries = [];
1334
+ const path = action === "list" ? resolveSecretListPrefix(options) : resolveSecretPath(name, options).permissionPaths.vault;
1335
+ entries.push({
1336
+ service: "tinycloud.kv",
1337
+ space: SECRETS_SPACE,
1338
+ path,
1339
+ actions: [secretActionName(action)],
1340
+ skipPrefix: true
1341
+ });
1342
+ if (action === "get" && encryptionNetworkId !== void 0) {
1343
+ entries.push({
1344
+ service: "tinycloud.encryption",
1345
+ path: encryptionNetworkId,
1346
+ actions: ["decrypt"],
1263
1347
  skipPrefix: true
1264
- }
1265
- ];
1266
- }
1267
- function isSecretsSpace(space) {
1268
- return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1348
+ });
1349
+ }
1350
+ return entries;
1269
1351
  }
1270
1352
  var NodeSecretsService = class {
1271
1353
  constructor(config) {
@@ -1293,48 +1375,62 @@ var NodeSecretsService = class {
1293
1375
  this.shouldRestoreUnlock = false;
1294
1376
  this.service.lock();
1295
1377
  }
1296
- get(name) {
1297
- return this.service.get(name);
1378
+ async get(name, options) {
1379
+ const permission = await this.ensurePermission(name, options, "get");
1380
+ if (!permission.ok) return permission;
1381
+ return options === void 0 ? this.service.get(name) : this.service.get(name, options);
1298
1382
  }
1299
- async put(name, value) {
1300
- const permission = await this.ensureMutationPermission(name, "put");
1383
+ async put(name, value, options) {
1384
+ const permission = await this.ensurePermission(name, options, "put");
1301
1385
  if (!permission.ok) return permission;
1302
- return this.service.put(name, value);
1386
+ return options === void 0 ? this.service.put(name, value) : this.service.put(name, value, options);
1303
1387
  }
1304
- async delete(name) {
1305
- const permission = await this.ensureMutationPermission(name, "del");
1388
+ async delete(name, options) {
1389
+ const permission = await this.ensurePermission(name, options, "del");
1306
1390
  if (!permission.ok) return permission;
1307
- return this.service.delete(name);
1391
+ return options === void 0 ? this.service.delete(name) : this.service.delete(name, options);
1308
1392
  }
1309
- list() {
1310
- return this.service.list();
1393
+ async list(options) {
1394
+ const permission = await this.ensurePermission("", options, "list");
1395
+ if (!permission.ok) return permission;
1396
+ return options === void 0 ? this.service.list() : this.service.list(options);
1311
1397
  }
1312
1398
  get service() {
1313
1399
  return this.config.getService();
1314
1400
  }
1315
- async ensureMutationPermission(name, action) {
1316
- if (!SECRET_NAME_RE.test(name)) {
1401
+ async ensurePermission(name, options, action) {
1402
+ const target = name || "secrets";
1403
+ let permissionEntries;
1404
+ try {
1405
+ permissionEntries = secretPermissionEntries(
1406
+ name,
1407
+ options,
1408
+ action,
1409
+ action === "get" ? this.config.getEncryptionNetworkId?.() : void 0
1410
+ );
1411
+ } catch (error) {
1317
1412
  return secretsError(
1318
1413
  ErrorCodes.INVALID_INPUT,
1319
- `Invalid secret name ${JSON.stringify(name)}. Secret names must match ${SECRET_NAME_RE.source}.`
1414
+ error instanceof Error ? error.message : String(error),
1415
+ error instanceof Error ? error : void 0
1320
1416
  );
1321
1417
  }
1322
- if (this.hasMutationPermission(name, action)) {
1418
+ if (this.hasPermission(permissionEntries)) {
1323
1419
  return ok();
1324
1420
  }
1325
1421
  if (!this.config.canEscalate()) {
1326
1422
  return secretsError(
1327
1423
  ErrorCodes.PERMISSION_DENIED,
1328
- `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1424
+ `Cannot autosign ${displayActionUrn(action)} for ${target}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1329
1425
  );
1330
1426
  }
1331
1427
  try {
1332
- await this.config.grantPermissions(secretPermissionEntries(name, action));
1428
+ await this.config.grantPermissions(permissionEntries);
1333
1429
  return this.restoreUnlockAfterEscalation();
1334
1430
  } catch (error) {
1335
1431
  return secretsError(
1336
1432
  ErrorCodes.PERMISSION_DENIED,
1337
- error instanceof Error ? error.message : `Autosign escalation for ${actionUrn(action)} on ${name} failed.`,
1433
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${target} failed.`,
1338
1434
  error instanceof Error ? error : void 0
1339
1435
  );
1340
1436
  }
@@ -1345,26 +1441,117 @@ var NodeSecretsService = class {
1345
1441
  }
1346
1442
  return this.service.unlock(this.unlockSigner);
1347
1443
  }
1348
- hasMutationPermission(name, action) {
1444
+ hasPermission(permissionEntries) {
1445
+ if (this.config.hasPermissions?.(permissionEntries)) {
1446
+ return true;
1447
+ }
1349
1448
  const manifest = this.config.getManifest();
1350
1449
  if (manifest === void 0) {
1351
1450
  return false;
1352
1451
  }
1353
1452
  const manifests = Array.isArray(manifest) ? manifest : [manifest];
1354
- const requiredAction = actionUrn(action);
1355
- return manifests.some((entry) => {
1356
- const resolved = resolveManifest(entry);
1357
- return ["keys", "vault"].every(
1358
- (base) => resolved.resources.some(
1359
- (resource) => resource.service === "tinycloud.kv" && isSecretsSpace(resource.space) && resource.path === secretResourcePath(base, name) && resource.actions.includes(requiredAction)
1360
- )
1361
- );
1362
- });
1453
+ const requestedEntries = expandPermissionEntries(permissionEntries);
1454
+ return requestedEntries.every(
1455
+ (entry) => manifests.some((candidate) => {
1456
+ const resolved = resolveManifest(candidate);
1457
+ return resolved.resources.some(
1458
+ (resource) => resource.service === entry.service && resource.space === entry.space && resource.path === entry.path && entry.actions.every((action) => resource.actions.includes(action))
1459
+ );
1460
+ })
1461
+ );
1363
1462
  }
1364
1463
  };
1365
1464
 
1366
1465
  // src/TinyCloudNode.ts
1367
1466
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
1467
+ var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
1468
+ var NETWORK_CREATE_ACTION = "tinycloud.encryption/network.create";
1469
+ var DECRYPT_ACTION = "tinycloud.encryption/decrypt";
1470
+ var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
1471
+ var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
1472
+ function base64UrlEncode(bytes) {
1473
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1474
+ let output = "";
1475
+ for (let i = 0; i < bytes.length; i += 3) {
1476
+ const a = bytes[i];
1477
+ const b = bytes[i + 1];
1478
+ const c = bytes[i + 2];
1479
+ const triplet = a << 16 | (b ?? 0) << 8 | (c ?? 0);
1480
+ output += alphabet[triplet >> 18 & 63];
1481
+ output += alphabet[triplet >> 12 & 63];
1482
+ if (i + 1 < bytes.length) output += alphabet[triplet >> 6 & 63];
1483
+ if (i + 2 < bytes.length) output += alphabet[triplet & 63];
1484
+ }
1485
+ return output;
1486
+ }
1487
+ function base64UrlDecode(value) {
1488
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1489
+ const bytes = [];
1490
+ let buffer = 0;
1491
+ let bits = 0;
1492
+ for (const char of value) {
1493
+ const index = alphabet.indexOf(char);
1494
+ if (index < 0) {
1495
+ throw new Error("invalid base64url input");
1496
+ }
1497
+ buffer = buffer << 6 | index;
1498
+ bits += 6;
1499
+ if (bits >= 8) {
1500
+ bits -= 8;
1501
+ bytes.push(buffer >> bits & 255);
1502
+ }
1503
+ }
1504
+ return new Uint8Array(bytes);
1505
+ }
1506
+ async function signJwtInputWithJwk(signingInput, jwk) {
1507
+ const bytes = new TextEncoder().encode(signingInput);
1508
+ try {
1509
+ const subtle = globalThis.crypto?.subtle;
1510
+ if (!subtle) {
1511
+ throw new Error("WebCrypto subtle API is unavailable");
1512
+ }
1513
+ const key = await subtle.importKey(
1514
+ "jwk",
1515
+ jwk,
1516
+ { name: "Ed25519" },
1517
+ false,
1518
+ ["sign"]
1519
+ );
1520
+ return new Uint8Array(await subtle.sign({ name: "Ed25519" }, key, bytes));
1521
+ } catch {
1522
+ const nodeCrypto = await import("crypto");
1523
+ const key = nodeCrypto.createPrivateKey({ key: jwk, format: "jwk" });
1524
+ return new Uint8Array(nodeCrypto.sign(null, Buffer.from(bytes), key));
1525
+ }
1526
+ }
1527
+ async function rewriteInvocationAudience(authorization, audience, jwk) {
1528
+ const [headerPart, payloadPart] = authorization.split(".");
1529
+ if (!headerPart || !payloadPart) {
1530
+ throw new Error("invalid invocation authorization");
1531
+ }
1532
+ const header = JSON.parse(new TextDecoder().decode(base64UrlDecode(headerPart)));
1533
+ const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(payloadPart)));
1534
+ payload.aud = audience;
1535
+ const signingInput = `${base64UrlEncode(
1536
+ new TextEncoder().encode(JSON.stringify(header))
1537
+ )}.${base64UrlEncode(new TextEncoder().encode(JSON.stringify(payload)))}`;
1538
+ const signature = await signJwtInputWithJwk(signingInput, jwk);
1539
+ return `${signingInput}.${base64UrlEncode(signature)}`;
1540
+ }
1541
+ function authorizationHeader(headers) {
1542
+ if (Array.isArray(headers)) {
1543
+ const entry = headers.find(([name]) => name.toLowerCase() === "authorization");
1544
+ if (!entry) {
1545
+ throw new Error("network invocation did not include an Authorization header");
1546
+ }
1547
+ return entry[1];
1548
+ }
1549
+ const value = headers.Authorization ?? headers.authorization;
1550
+ if (!value) {
1551
+ throw new Error("network invocation did not include an Authorization header");
1552
+ }
1553
+ return value;
1554
+ }
1368
1555
  var _TinyCloudNode = class _TinyCloudNode {
1369
1556
  /**
1370
1557
  * Create a new TinyCloudNode instance.
@@ -1409,12 +1596,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1409
1596
  throw new Error("WASM binding does not support invokeAny");
1410
1597
  }
1411
1598
  const grant = this.findGrantForOperations(
1412
- entries.map((entry) => ({
1413
- spaceId: entry.spaceId,
1414
- service: this.invocationServiceName(entry.service),
1415
- path: entry.path,
1416
- action: entry.action
1417
- }))
1599
+ entries.flatMap((entry) => {
1600
+ const operation = this.operationFromInvokeAnyEntry(entry);
1601
+ return operation ? [operation] : [];
1602
+ })
1418
1603
  );
1419
1604
  return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1420
1605
  };
@@ -1507,7 +1692,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1507
1692
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
1508
1693
  domain: this.siweDomain,
1509
1694
  spacePrefix: config.prefix,
1510
- sessionExpirationMs: config.sessionExpirationMs ?? 60 * 60 * 1e3,
1695
+ sessionExpirationMs: config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1511
1696
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1512
1697
  tinycloudRegistryUrl: config.tinycloudRegistryUrl,
1513
1698
  tinycloudFallbackHosts: config.tinycloudFallbackHosts,
@@ -1642,6 +1827,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1642
1827
  this._duckdb = void 0;
1643
1828
  this._hooks = void 0;
1644
1829
  this._vault = void 0;
1830
+ this._encryption = void 0;
1645
1831
  this._baseSecrets = void 0;
1646
1832
  this._secrets = void 0;
1647
1833
  this._spaceService = void 0;
@@ -1650,6 +1836,9 @@ var _TinyCloudNode = class _TinyCloudNode {
1650
1836
  await this.tc.signIn(options);
1651
1837
  this.syncResolvedHostFromAuth();
1652
1838
  this.initializeServices();
1839
+ if (this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
1840
+ await this.ensureOwnedSpaceHosted(this.ownedSpaceId("secrets"));
1841
+ }
1653
1842
  await this.writeManifestRegistryRecords();
1654
1843
  this.notificationHandler.success("Successfully signed in");
1655
1844
  }
@@ -1732,6 +1921,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1732
1921
  this._duckdb = void 0;
1733
1922
  this._hooks = void 0;
1734
1923
  this._vault = void 0;
1924
+ this._encryption = void 0;
1735
1925
  this._baseSecrets = void 0;
1736
1926
  this._secrets = void 0;
1737
1927
  this._spaceService = void 0;
@@ -1773,6 +1963,33 @@ var _TinyCloudNode = class _TinyCloudNode {
1773
1963
  this._vault.initialize(this._serviceContext);
1774
1964
  this._serviceContext.registerService("vault", this._vault);
1775
1965
  this.initializeV2Services(serviceSession);
1966
+ if (sessionData.siwe && sessionData.address && sessionData.chainId) {
1967
+ const tcSession = {
1968
+ address: sessionData.address,
1969
+ chainId: sessionData.chainId,
1970
+ sessionKey: JSON.stringify(sessionData.jwk),
1971
+ spaceId: sessionData.spaceId,
1972
+ delegationCid: sessionData.delegationCid,
1973
+ delegationHeader: sessionData.delegationHeader,
1974
+ verificationMethod: sessionData.verificationMethod,
1975
+ jwk: sessionData.jwk,
1976
+ siwe: sessionData.siwe,
1977
+ signature: sessionData.signature ?? ""
1978
+ };
1979
+ if (this.auth) {
1980
+ this.auth.setRestoredTinyCloudSession(tcSession);
1981
+ } else {
1982
+ this._restoredTcSession = tcSession;
1983
+ }
1984
+ }
1985
+ }
1986
+ /**
1987
+ * Resolve the currently-active TinyCloudSession, preferring the auth
1988
+ * layer's value (wallet mode) and falling back to the node-level
1989
+ * rehydration set by {@link restoreSession} (session-only mode).
1990
+ */
1991
+ currentTinyCloudSession() {
1992
+ return this.auth?.tinyCloudSession ?? this._restoredTcSession;
1776
1993
  }
1777
1994
  /**
1778
1995
  * Connect a wallet to upgrade from session-only mode to wallet mode.
@@ -1817,7 +2034,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1817
2034
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
1818
2035
  domain: this.siweDomain,
1819
2036
  spacePrefix: prefix,
1820
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
2037
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1821
2038
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1822
2039
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1823
2040
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
@@ -1861,7 +2078,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1861
2078
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
1862
2079
  domain: this.siweDomain,
1863
2080
  spacePrefix: prefix,
1864
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
2081
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1865
2082
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1866
2083
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1867
2084
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
@@ -1884,7 +2101,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1884
2101
  * @internal
1885
2102
  */
1886
2103
  initializeServices() {
1887
- const session = this.auth?.tinyCloudSession;
2104
+ const session = this.currentTinyCloudSession();
1888
2105
  if (!session) {
1889
2106
  return;
1890
2107
  }
@@ -1942,6 +2159,163 @@ var _TinyCloudNode = class _TinyCloudNode {
1942
2159
  }
1943
2160
  return kvService;
1944
2161
  }
2162
+ getDefaultEncryptionNetworkId(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
2163
+ return `urn:tinycloud:encryption:${this.did}:${name}`;
2164
+ }
2165
+ requireServiceSession() {
2166
+ const session = this._serviceContext?.session;
2167
+ if (!session) {
2168
+ throw new Error("Not signed in. Call signIn() first.");
2169
+ }
2170
+ return session;
2171
+ }
2172
+ createEncryptionCrypto() {
2173
+ const wasm = this.wasmBindings;
2174
+ const columnEncrypt = (key, plaintext) => {
2175
+ const encrypted = wasm.vault_encrypt(key, plaintext);
2176
+ const out = new Uint8Array(1 + encrypted.length);
2177
+ out[0] = 1;
2178
+ out.set(encrypted, 1);
2179
+ return out;
2180
+ };
2181
+ const columnDecrypt = (key, blob) => {
2182
+ if (blob[0] !== 1) {
2183
+ return blob;
2184
+ }
2185
+ return wasm.vault_decrypt(key, blob.slice(1));
2186
+ };
2187
+ return {
2188
+ sha256: (data) => wasm.vault_sha256(data),
2189
+ randomBytes: (length) => wasm.vault_random_bytes(length),
2190
+ x25519FromSeed: (seed) => wasm.vault_x25519_from_seed(seed),
2191
+ x25519Dh: (privateKey, publicKey) => wasm.vault_x25519_dh(privateKey, publicKey),
2192
+ authEncrypt: (key, plaintext) => wasm.vault_encrypt(key, plaintext),
2193
+ authDecrypt: (key, ciphertext) => wasm.vault_decrypt(key, ciphertext),
2194
+ sealToNetworkKey: (networkPublicKey, symmetricKey) => {
2195
+ const seed = wasm.vault_random_bytes(32);
2196
+ const ephemeral = wasm.vault_x25519_from_seed(seed);
2197
+ const shared = wasm.vault_x25519_dh(
2198
+ ephemeral.privateKey,
2199
+ networkPublicKey
2200
+ );
2201
+ const encrypted = columnEncrypt(shared, symmetricKey);
2202
+ const out = new Uint8Array(ephemeral.publicKey.length + encrypted.length);
2203
+ out.set(ephemeral.publicKey, 0);
2204
+ out.set(encrypted, ephemeral.publicKey.length);
2205
+ return out;
2206
+ },
2207
+ openWithReceiverKey: (receiverPrivateKey, wrappedKey) => {
2208
+ const peerPublic = wrappedKey.slice(0, 32);
2209
+ const ciphertext = wrappedKey.slice(32);
2210
+ const shared = wasm.vault_x25519_dh(receiverPrivateKey, peerPublic);
2211
+ return columnDecrypt(shared, ciphertext);
2212
+ },
2213
+ verifyNodeSignature: (nodeId, message, signature) => verifyDidKeyEd25519Signature(nodeId, message, signature)
2214
+ };
2215
+ }
2216
+ async fetchNodeId() {
2217
+ const response = await fetch(`${this.config.host}/info`);
2218
+ if (!response.ok) {
2219
+ throw new Error(`Failed to fetch node info: HTTP ${response.status}`);
2220
+ }
2221
+ const info = await response.json();
2222
+ if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
2223
+ throw new Error("Node /info response did not include nodeId");
2224
+ }
2225
+ return info.nodeId;
2226
+ }
2227
+ async signRawNetworkAuthorization(input) {
2228
+ if (!this.wasmBindings.invokeAny) {
2229
+ throw new Error("WASM binding does not support raw-resource invokeAny");
2230
+ }
2231
+ if (!this.wasmBindings.computeCid) {
2232
+ throw new Error("WASM binding does not support invocation CID computation");
2233
+ }
2234
+ const session = this.requireServiceSession();
2235
+ const headers = this.invokeAnyWithRuntimePermissions(
2236
+ session,
2237
+ [
2238
+ {
2239
+ resource: input.networkId,
2240
+ service: "encryption",
2241
+ path: input.networkId,
2242
+ action: input.action
2243
+ }
2244
+ ],
2245
+ [input.facts]
2246
+ );
2247
+ const authorization = authorizationHeader(headers);
2248
+ const audienceBound = await rewriteInvocationAudience(
2249
+ authorization,
2250
+ input.targetNode,
2251
+ session.jwk
2252
+ );
2253
+ return {
2254
+ authorization: audienceBound,
2255
+ invocationCid: this.wasmBindings.computeCid(
2256
+ new TextEncoder().encode(audienceBound),
2257
+ 0x55n
2258
+ )
2259
+ };
2260
+ }
2261
+ createEncryptionService() {
2262
+ const crypto = this.createEncryptionCrypto();
2263
+ const transport = {
2264
+ postDecrypt: async ({ networkId, authorization, canonicalBody }) => {
2265
+ const response = await fetch(
2266
+ `${this.config.host}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
2267
+ {
2268
+ method: "POST",
2269
+ headers: {
2270
+ Authorization: authorization,
2271
+ "Content-Type": "application/json"
2272
+ },
2273
+ body: canonicalBody
2274
+ }
2275
+ );
2276
+ if (!response.ok) {
2277
+ throw new Error(
2278
+ `decrypt failed ${response.status}: ${await response.text()}`
2279
+ );
2280
+ }
2281
+ return await response.json();
2282
+ }
2283
+ };
2284
+ return new EncryptionService({
2285
+ crypto,
2286
+ signer: {
2287
+ signDecryptInvocation: async (input) => {
2288
+ const signed = await this.signRawNetworkAuthorization({
2289
+ targetNode: input.targetNode,
2290
+ networkId: input.networkId,
2291
+ action: DECRYPT_ACTION,
2292
+ facts: input.facts
2293
+ });
2294
+ return {
2295
+ ...signed,
2296
+ canonicalBody: canonicalizeEncryptionJson(
2297
+ input.body
2298
+ )
2299
+ };
2300
+ }
2301
+ },
2302
+ transport,
2303
+ node: {
2304
+ fetchByNetworkId: (networkId) => this.getEncryptionNetwork(networkId)
2305
+ }
2306
+ });
2307
+ }
2308
+ getEncryptionService() {
2309
+ if (!this._serviceContext) {
2310
+ throw new Error("Not signed in. Call signIn() first.");
2311
+ }
2312
+ if (!this._encryption) {
2313
+ this._encryption = this.createEncryptionService();
2314
+ this._encryption.initialize(this._serviceContext);
2315
+ this._serviceContext.registerService("encryption", this._encryption);
2316
+ }
2317
+ return this._encryption;
2318
+ }
1945
2319
  createVaultService(spaceId, kv) {
1946
2320
  const wasm = this.wasmBindings;
1947
2321
  const vaultCrypto = createVaultCrypto({
@@ -1957,6 +2331,13 @@ var _TinyCloudNode = class _TinyCloudNode {
1957
2331
  return new DataVaultService({
1958
2332
  spaceId,
1959
2333
  crypto: vaultCrypto,
2334
+ encryption: {
2335
+ networkId: this.getDefaultEncryptionNetworkId(),
2336
+ service: this.getEncryptionService(),
2337
+ decryptCapabilityProof: () => ({
2338
+ proofs: [this.requireServiceSession().delegationCid]
2339
+ })
2340
+ },
1960
2341
  tc: {
1961
2342
  kv,
1962
2343
  ensurePublicSpace: async () => {
@@ -2137,7 +2518,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2137
2518
  * @internal
2138
2519
  */
2139
2520
  getSessionExpiry() {
2140
- const expirationMs = this.config.sessionExpirationMs ?? 60 * 60 * 1e3;
2521
+ const expirationMs = this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS;
2141
2522
  return new Date(Date.now() + expirationMs);
2142
2523
  }
2143
2524
  /**
@@ -2194,7 +2575,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2194
2575
  if (!this.signer) {
2195
2576
  return void 0;
2196
2577
  }
2197
- const session = this.auth?.tinyCloudSession;
2578
+ const session = this.currentTinyCloudSession();
2198
2579
  if (!session) {
2199
2580
  return void 0;
2200
2581
  }
@@ -2294,6 +2675,34 @@ var _TinyCloudNode = class _TinyCloudNode {
2294
2675
  }
2295
2676
  return this._sql;
2296
2677
  }
2678
+ /**
2679
+ * Get an SQL service scoped to a specific space.
2680
+ *
2681
+ * Mirrors {@link SpaceService}'s per-space KV factory: clones the active
2682
+ * service context and overrides its session's spaceId so that subsequent
2683
+ * `sql/<dbName>/<action>` invocations route to that space. Useful when
2684
+ * the caller already holds a delegation covering the target space (e.g.
2685
+ * via {@link grantRuntimePermissions} or {@link useRuntimeDelegation})
2686
+ * but the SDK's per-space SQL surface isn't otherwise exposed.
2687
+ *
2688
+ * Does NOT auto-create the space.
2689
+ *
2690
+ * @param spaceId - Full space URI (`tinycloud:pkh:eip155:<chain>:<addr>:<name>`).
2691
+ */
2692
+ sqlForSpace(spaceId) {
2693
+ if (!this._serviceContext || !this._serviceContext.session) {
2694
+ throw new Error("Not signed in. Call signIn() first.");
2695
+ }
2696
+ const sql = new SQLService2({});
2697
+ const spaceScopedContext = new ServiceContext2({
2698
+ invoke: this._serviceContext.invoke,
2699
+ fetch: this._serviceContext.fetch,
2700
+ hosts: this._serviceContext.hosts
2701
+ });
2702
+ spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
2703
+ sql.initialize(spaceScopedContext);
2704
+ return sql;
2705
+ }
2297
2706
  /**
2298
2707
  * DuckDB database operations on this user's space.
2299
2708
  */
@@ -2317,6 +2726,79 @@ var _TinyCloudNode = class _TinyCloudNode {
2317
2726
  }
2318
2727
  return this._vault;
2319
2728
  }
2729
+ /**
2730
+ * Network-scoped encryption/decrypt service.
2731
+ */
2732
+ get encryption() {
2733
+ return this.getEncryptionService();
2734
+ }
2735
+ async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
2736
+ const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
2737
+ const response = await fetch(
2738
+ `${this.config.host}/encryption/networks/${encodeURIComponent(networkId)}`
2739
+ );
2740
+ if (response.status === 404) {
2741
+ return null;
2742
+ }
2743
+ if (!response.ok) {
2744
+ throw new Error(
2745
+ `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
2746
+ );
2747
+ }
2748
+ const body = await response.json();
2749
+ return "descriptor" in body && body.descriptor ? body.descriptor : body;
2750
+ }
2751
+ async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
2752
+ const targetNode = await this.fetchNodeId();
2753
+ const principal = this.did;
2754
+ const networkId = this.getDefaultEncryptionNetworkId(name);
2755
+ const body = {
2756
+ name,
2757
+ principal,
2758
+ threshold: { n: 1, t: 1 }
2759
+ };
2760
+ const crypto = this.createEncryptionCrypto();
2761
+ const facts = {
2762
+ type: NETWORK_ADMIN_TYPE,
2763
+ targetNode,
2764
+ networkId,
2765
+ bodyHash: canonicalHashHex(
2766
+ crypto.sha256,
2767
+ body
2768
+ ),
2769
+ action: NETWORK_CREATE_ACTION
2770
+ };
2771
+ const signed = await this.signRawNetworkAuthorization({
2772
+ targetNode,
2773
+ networkId,
2774
+ action: NETWORK_CREATE_ACTION,
2775
+ facts
2776
+ });
2777
+ const response = await fetch(`${this.config.host}/encryption/networks`, {
2778
+ method: "POST",
2779
+ headers: {
2780
+ Authorization: signed.authorization,
2781
+ "Content-Type": "application/json"
2782
+ },
2783
+ body: canonicalizeEncryptionJson(
2784
+ body
2785
+ )
2786
+ });
2787
+ if (!response.ok) {
2788
+ throw new Error(
2789
+ `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
2790
+ );
2791
+ }
2792
+ const created = await response.json();
2793
+ return created.descriptor;
2794
+ }
2795
+ async ensureEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
2796
+ const existing = await this.getEncryptionNetwork(name);
2797
+ if (existing) {
2798
+ return existing;
2799
+ }
2800
+ return this.createEncryptionNetwork(name);
2801
+ }
2320
2802
  /**
2321
2803
  * App-facing secrets API backed by the `secrets` space vault.
2322
2804
  */
@@ -2328,8 +2810,10 @@ var _TinyCloudNode = class _TinyCloudNode {
2328
2810
  this._secrets = new NodeSecretsService({
2329
2811
  getService: () => this.getBaseSecrets(),
2330
2812
  getManifest: () => this.manifest,
2813
+ hasPermissions: (permissions) => this.hasRuntimePermissions(permissions),
2331
2814
  grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2332
2815
  canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2816
+ getEncryptionNetworkId: () => this.getDefaultEncryptionNetworkId(),
2333
2817
  getUnlockSigner: () => this.signer ?? void 0
2334
2818
  });
2335
2819
  }
@@ -2419,7 +2903,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2419
2903
  * every requested permission.
2420
2904
  */
2421
2905
  hasRuntimePermissions(permissions) {
2422
- const session = this.auth?.tinyCloudSession;
2906
+ const session = this.currentTinyCloudSession();
2423
2907
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2424
2908
  return false;
2425
2909
  }
@@ -2439,7 +2923,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2439
2923
  if (permissions === void 0) {
2440
2924
  return this.runtimePermissionGrants.map((grant) => grant.delegation);
2441
2925
  }
2442
- const session = this.auth?.tinyCloudSession;
2926
+ const session = this.currentTinyCloudSession();
2443
2927
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2444
2928
  return [];
2445
2929
  }
@@ -2453,7 +2937,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2453
2937
  * matching service calls and downstream `delegateTo()` calls can use it.
2454
2938
  */
2455
2939
  async useRuntimeDelegation(delegation) {
2456
- const session = this.auth?.tinyCloudSession;
2940
+ const session = this.currentTinyCloudSession();
2457
2941
  if (!session) {
2458
2942
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2459
2943
  }
@@ -2492,7 +2976,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2492
2976
  if (!Array.isArray(permissions) || permissions.length === 0) {
2493
2977
  throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2494
2978
  }
2495
- const session = this.auth?.tinyCloudSession;
2979
+ const session = this.currentTinyCloudSession();
2496
2980
  if (!session) {
2497
2981
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2498
2982
  }
@@ -2516,13 +3000,22 @@ var _TinyCloudNode = class _TinyCloudNode {
2516
3000
  "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2517
3001
  );
2518
3002
  }
3003
+ const rawEntries = expanded.filter(
3004
+ (entry) => this.isEncryptionPermissionEntry(entry)
3005
+ );
3006
+ const spaceEntries = expanded.filter(
3007
+ (entry) => !this.isEncryptionPermissionEntry(entry)
3008
+ );
2519
3009
  const bySpace = /* @__PURE__ */ new Map();
2520
- for (const entry of expanded) {
3010
+ for (const entry of spaceEntries) {
2521
3011
  const spaceId = this.resolvePermissionSpace(entry.space, session);
2522
3012
  const current = bySpace.get(spaceId) ?? [];
2523
3013
  current.push(entry);
2524
3014
  bySpace.set(spaceId, current);
2525
3015
  }
3016
+ if (bySpace.size === 0 && rawEntries.length > 0) {
3017
+ bySpace.set(session.spaceId, []);
3018
+ }
2526
3019
  const now = /* @__PURE__ */ new Date();
2527
3020
  const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2528
3021
  let expiresAt = new Date(now.getTime() + requestedExpiryMs);
@@ -2530,10 +3023,17 @@ var _TinyCloudNode = class _TinyCloudNode {
2530
3023
  expiresAt = sessionExpiry;
2531
3024
  }
2532
3025
  const delegations = [];
3026
+ let rawEntriesAttached = false;
2533
3027
  for (const [spaceId, entries] of bySpace) {
3028
+ const rawForDelegation = !rawEntriesAttached ? rawEntries : [];
3029
+ if (rawForDelegation.length > 0) {
3030
+ rawEntriesAttached = true;
3031
+ }
3032
+ const delegatedEntries = [...entries, ...rawForDelegation];
2534
3033
  const abilities = this.permissionsToAbilities(entries);
2535
3034
  const prepared = this.wasmBindings.prepareSession({
2536
3035
  abilities,
3036
+ ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
2537
3037
  address: this.wasmBindings.ensureEip55(session.address),
2538
3038
  chainId: session.chainId,
2539
3039
  domain: this.siweDomain,
@@ -2558,7 +3058,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2558
3058
  }
2559
3059
  const delegation = this.runtimeDelegationFromSession(
2560
3060
  delegatedSession,
2561
- entries,
3061
+ delegatedEntries,
2562
3062
  spaceId,
2563
3063
  session,
2564
3064
  expiresAt
@@ -2572,7 +3072,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2572
3072
  jwk: session.jwk
2573
3073
  },
2574
3074
  delegation,
2575
- operations: this.permissionOperations(entries, spaceId),
3075
+ operations: this.permissionOperations(delegatedEntries, spaceId),
2576
3076
  expiresAt
2577
3077
  });
2578
3078
  delegations.push(delegation);
@@ -2720,7 +3220,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2720
3220
  ];
2721
3221
  const abilities = { kv: { "": kvActions } };
2722
3222
  const now = /* @__PURE__ */ new Date();
2723
- const expiryMs = 60 * 60 * 1e3;
3223
+ const expiryMs = EXPIRY3.EPHEMERAL_MS;
2724
3224
  const expirationTime = new Date(now.getTime() + expiryMs);
2725
3225
  const prepared = this.wasmBindings.prepareSession({
2726
3226
  abilities,
@@ -2890,7 +3390,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2890
3390
  * `forceWalletSign` is not set.
2891
3391
  */
2892
3392
  async delegateTo(did, permissions, options) {
2893
- const session = this.auth?.tinyCloudSession;
3393
+ const session = this.currentTinyCloudSession();
2894
3394
  if (!session) {
2895
3395
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2896
3396
  }
@@ -2907,10 +3407,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2907
3407
  "delegateTo requires a non-empty permissions array"
2908
3408
  );
2909
3409
  }
2910
- const expandedEntries = permissions.map((entry) => ({
2911
- ...entry,
2912
- actions: expandActionShortNames(entry.service, entry.actions)
2913
- }));
3410
+ const expandedEntries = this.expandPermissionEntries(permissions);
2914
3411
  const now = /* @__PURE__ */ new Date();
2915
3412
  const expiryMs = resolveExpiryMs(options?.expiry);
2916
3413
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -3007,11 +3504,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3007
3504
  * the current session; we build one multi-resource abilities map
3008
3505
  * and emit one signed UCAN covering them all.
3009
3506
  *
3010
- * All entries must share the same target space (the UCAN is
3011
- * scoped to a single space). If they don't, this throws mixing
3012
- * spaces in a single delegation is not supported by the underlying
3013
- * Rust create_delegation call and the resulting UCAN would be
3014
- * under-specified.
3507
+ * Non-encryption entries must share the same target space. Encryption
3508
+ * entries are raw network URNs and do not participate in space grouping.
3015
3509
  *
3016
3510
  * @internal
3017
3511
  */
@@ -3023,15 +3517,18 @@ var _TinyCloudNode = class _TinyCloudNode {
3023
3517
  }
3024
3518
  const resolvedSpaces = /* @__PURE__ */ new Set();
3025
3519
  for (const entry of entries) {
3520
+ if (this.isEncryptionPermissionEntry(entry)) {
3521
+ continue;
3522
+ }
3026
3523
  const spaceId2 = this.resolvePermissionSpace(entry.space, session);
3027
3524
  resolvedSpaces.add(spaceId2);
3028
3525
  }
3029
- if (resolvedSpaces.size !== 1) {
3526
+ if (resolvedSpaces.size > 1) {
3030
3527
  throw new Error(
3031
3528
  `delegateTo: all permission entries must target the same space, got ${resolvedSpaces.size}: ${JSON.stringify([...resolvedSpaces])}`
3032
3529
  );
3033
3530
  }
3034
- const spaceId = [...resolvedSpaces][0];
3531
+ const spaceId = resolvedSpaces.size === 1 ? [...resolvedSpaces][0] : session.spaceId;
3035
3532
  const abilities = {};
3036
3533
  for (const entry of entries) {
3037
3534
  const shortService = SERVICE_LONG_TO_SHORT[entry.service];
@@ -3150,10 +3647,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3150
3647
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
3151
3648
  }
3152
3649
  expandPermissionEntries(permissions) {
3153
- return permissions.map((entry) => ({
3154
- ...entry,
3155
- actions: expandActionShortNames(entry.service, entry.actions)
3156
- }));
3650
+ return expandPermissionEntriesCore(permissions);
3157
3651
  }
3158
3652
  shortServiceName(service) {
3159
3653
  const short = SERVICE_LONG_TO_SHORT[service];
@@ -3181,11 +3675,32 @@ var _TinyCloudNode = class _TinyCloudNode {
3181
3675
  }
3182
3676
  return abilities;
3183
3677
  }
3678
+ isEncryptionPermissionEntry(entry) {
3679
+ return entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.path.startsWith("urn:tinycloud:encryption:");
3680
+ }
3681
+ permissionsToRawAbilities(entries) {
3682
+ const rawAbilities = {};
3683
+ for (const entry of entries) {
3684
+ if (!this.isEncryptionPermissionEntry(entry)) {
3685
+ continue;
3686
+ }
3687
+ const existing = rawAbilities[entry.path] ?? [];
3688
+ const seen = new Set(existing);
3689
+ for (const action of entry.actions) {
3690
+ if (!seen.has(action)) {
3691
+ existing.push(action);
3692
+ seen.add(action);
3693
+ }
3694
+ }
3695
+ rawAbilities[entry.path] = existing;
3696
+ }
3697
+ return rawAbilities;
3698
+ }
3184
3699
  permissionOperations(entries, spaceId) {
3185
3700
  return entries.flatMap((entry) => {
3186
3701
  const service = this.shortServiceName(entry.service);
3187
3702
  return entry.actions.map((action) => ({
3188
- spaceId,
3703
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
3189
3704
  service,
3190
3705
  path: entry.path,
3191
3706
  action
@@ -3208,7 +3723,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3208
3723
  const spaceId = this.resolvePermissionSpace(entry.space, session);
3209
3724
  const service = this.shortServiceName(entry.service);
3210
3725
  return entry.actions.map((action) => ({
3211
- spaceId,
3726
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
3212
3727
  service,
3213
3728
  path: entry.path,
3214
3729
  action
@@ -3265,24 +3780,40 @@ var _TinyCloudNode = class _TinyCloudNode {
3265
3780
  expiresAt: delegation.expiry
3266
3781
  };
3267
3782
  }
3783
+ installRuntimeGrantFromServiceSession(delegation, session, expiresAt) {
3784
+ const operations = this.operationsFromDelegation(delegation);
3785
+ if (operations.length === 0) {
3786
+ return;
3787
+ }
3788
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3789
+ (grant) => grant.delegation.cid !== delegation.cid && grant.session.delegationCid !== session.delegationCid
3790
+ );
3791
+ this.runtimePermissionGrants.push({
3792
+ session,
3793
+ delegation,
3794
+ operations,
3795
+ expiresAt
3796
+ });
3797
+ }
3268
3798
  delegatedResourcesForEntries(entries, spaceId) {
3269
3799
  return entries.map((entry) => ({
3270
3800
  service: this.shortServiceName(entry.service),
3271
- space: spaceId,
3801
+ space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
3272
3802
  path: entry.path,
3273
3803
  actions: [...entry.actions]
3274
3804
  }));
3275
3805
  }
3276
3806
  operationsFromDelegation(delegation) {
3277
3807
  const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3278
- return resources.flatMap(
3279
- (resource) => resource.actions.map((action) => ({
3280
- spaceId: resource.space,
3281
- service: this.invocationServiceName(resource.service),
3808
+ return resources.flatMap((resource) => {
3809
+ const service = this.invocationServiceName(resource.service);
3810
+ return resource.actions.map((action) => ({
3811
+ ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
3812
+ service,
3282
3813
  path: resource.path,
3283
3814
  action
3284
- }))
3285
- );
3815
+ }));
3816
+ });
3286
3817
  }
3287
3818
  flatDelegationResources(delegation) {
3288
3819
  const byService = /* @__PURE__ */ new Map();
@@ -3331,7 +3862,13 @@ var _TinyCloudNode = class _TinyCloudNode {
3331
3862
  );
3332
3863
  }
3333
3864
  operationCovers(granted, requested) {
3334
- return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3865
+ if (granted.service !== requested.service || !this.actionContains(granted.action, requested.action)) {
3866
+ return false;
3867
+ }
3868
+ if (granted.resource !== void 0 || requested.resource !== void 0) {
3869
+ return granted.resource !== void 0 && requested.resource !== void 0 && granted.resource === requested.resource && this.pathContains(granted.path, requested.path);
3870
+ }
3871
+ return granted.spaceId !== void 0 && requested.spaceId !== void 0 && granted.spaceId === requested.spaceId && this.pathContains(granted.path, requested.path);
3335
3872
  }
3336
3873
  actionContains(grantedAction, requestedAction) {
3337
3874
  if (grantedAction === requestedAction) {
@@ -3346,6 +3883,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3346
3883
  invocationServiceName(service) {
3347
3884
  return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3348
3885
  }
3886
+ isEncryptionNetworkOperation(service, path) {
3887
+ return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
3888
+ }
3889
+ operationFromInvokeAnyEntry(entry) {
3890
+ const service = this.invocationServiceName(entry.service);
3891
+ if (typeof entry.resource === "string") {
3892
+ return {
3893
+ resource: entry.resource,
3894
+ service,
3895
+ path: entry.path,
3896
+ action: entry.action
3897
+ };
3898
+ }
3899
+ if (this.isEncryptionNetworkOperation(service, entry.path)) {
3900
+ return {
3901
+ resource: entry.path,
3902
+ service,
3903
+ path: entry.path,
3904
+ action: entry.action
3905
+ };
3906
+ }
3907
+ if (typeof entry.spaceId === "string") {
3908
+ return {
3909
+ spaceId: entry.spaceId,
3910
+ service,
3911
+ path: entry.path,
3912
+ action: entry.action
3913
+ };
3914
+ }
3915
+ return void 0;
3916
+ }
3349
3917
  pathContains(grantedPath, requestedPath) {
3350
3918
  if (grantedPath === "" || grantedPath === "/") {
3351
3919
  return true;
@@ -3584,6 +4152,17 @@ var _TinyCloudNode = class _TinyCloudNode {
3584
4152
  // Not used in session-only mode
3585
4153
  };
3586
4154
  this.trackReceivedDelegation(delegation, this.sessionKeyJwk);
4155
+ this.installRuntimeGrantFromServiceSession(
4156
+ delegation,
4157
+ {
4158
+ delegationHeader: session2.delegationHeader,
4159
+ delegationCid: session2.delegationCid,
4160
+ spaceId: session2.spaceId,
4161
+ verificationMethod: session2.verificationMethod,
4162
+ jwk: session2.jwk
4163
+ },
4164
+ delegation.expiry
4165
+ );
3587
4166
  return new DelegatedAccess(session2, delegation, targetHost, this.wasmBindings.invoke);
3588
4167
  }
3589
4168
  const mySession = this.auth?.tinyCloudSession;
@@ -3595,6 +4174,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3595
4174
  const kvActions = delegation.actions.filter((a) => a.startsWith("tinycloud.kv/"));
3596
4175
  const sqlActions = delegation.actions.filter((a) => a.startsWith("tinycloud.sql/"));
3597
4176
  const duckdbActions = delegation.actions.filter((a) => a.startsWith("tinycloud.duckdb/"));
4177
+ const encryptionActions = delegation.actions.filter(
4178
+ (a) => a.startsWith("tinycloud.encryption/")
4179
+ );
4180
+ const rawAbilities = {};
3598
4181
  if (kvActions.length > 0) {
3599
4182
  abilities.kv = { [delegation.path]: kvActions };
3600
4183
  }
@@ -3604,6 +4187,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3604
4187
  if (duckdbActions.length > 0) {
3605
4188
  abilities.duckdb = { [delegation.path]: duckdbActions };
3606
4189
  }
4190
+ if (encryptionActions.length > 0 && delegation.path.startsWith("urn:tinycloud:encryption:")) {
4191
+ rawAbilities[delegation.path] = encryptionActions;
4192
+ }
3607
4193
  const now = /* @__PURE__ */ new Date();
3608
4194
  const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
3609
4195
  const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
@@ -3616,7 +4202,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3616
4202
  expirationTime: expirationTime.toISOString(),
3617
4203
  spaceId: delegation.spaceId,
3618
4204
  jwk,
3619
- parents: [delegation.cid]
4205
+ parents: [delegation.cid],
4206
+ ...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
3620
4207
  });
3621
4208
  const signature = await this.signer.signMessage(prepared.siwe);
3622
4209
  const invokerSession = this.wasmBindings.completeSessionSetup({
@@ -3643,6 +4230,17 @@ var _TinyCloudNode = class _TinyCloudNode {
3643
4230
  signature
3644
4231
  };
3645
4232
  this.trackReceivedDelegation(delegation, jwk);
4233
+ this.installRuntimeGrantFromServiceSession(
4234
+ delegation,
4235
+ {
4236
+ delegationHeader: session.delegationHeader,
4237
+ delegationCid: session.delegationCid,
4238
+ spaceId: session.spaceId,
4239
+ verificationMethod: session.verificationMethod,
4240
+ jwk: session.jwk
4241
+ },
4242
+ expirationTime
4243
+ );
3646
4244
  return new DelegatedAccess(session, delegation, targetHost, this.wasmBindings.invoke);
3647
4245
  }
3648
4246
  /**
@@ -3753,6 +4351,7 @@ import {
3753
4351
  ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
3754
4352
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
3755
4353
  DEFAULT_MANIFEST_VERSION,
4354
+ VAULT_PERMISSION_SERVICE,
3756
4355
  PermissionNotInManifestError as PermissionNotInManifestError2,
3757
4356
  SessionExpiredError as SessionExpiredError2,
3758
4357
  ManifestValidationError,
@@ -3761,7 +4360,9 @@ import {
3761
4360
  validateManifest,
3762
4361
  loadManifest,
3763
4362
  isCapabilitySubset as isCapabilitySubset2,
3764
- expandActionShortNames as expandActionShortNames2,
4363
+ expandActionShortNames,
4364
+ expandPermissionEntries as expandPermissionEntries2,
4365
+ expandPermissionEntry,
3765
4366
  parseExpiry as parseExpiry2,
3766
4367
  resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
3767
4368
  } from "@tinycloud/sdk-core";
@@ -3783,10 +4384,24 @@ function deserializeDelegation(data) {
3783
4384
  }
3784
4385
 
3785
4386
  // src/core.ts
3786
- import { KVService as KVService3, PrefixedKVService } from "@tinycloud/sdk-core";
4387
+ import {
4388
+ DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
4389
+ KVService as KVService3,
4390
+ PrefixedKVService
4391
+ } from "@tinycloud/sdk-core";
3787
4392
  import { SQLService as SQLService3, SQLAction, DatabaseHandle } from "@tinycloud/sdk-core";
3788
4393
  import { DuckDbService as DuckDbService3, DuckDbDatabaseHandle, DuckDbAction } from "@tinycloud/sdk-core";
3789
- import { DataVaultService as DataVaultService2, VaultHeaders, VaultPublicSpaceKVActions, createVaultCrypto as createVaultCrypto2, SecretsService as SecretsService2 } from "@tinycloud/sdk-core";
4394
+ import {
4395
+ DataVaultService as DataVaultService2,
4396
+ VaultHeaders,
4397
+ VaultPublicSpaceKVActions,
4398
+ createVaultCrypto as createVaultCrypto2,
4399
+ SecretsService as SecretsService2,
4400
+ SECRET_NAME_RE,
4401
+ canonicalizeSecretScope,
4402
+ resolveSecretListPrefix as resolveSecretListPrefix2,
4403
+ resolveSecretPath as resolveSecretPath2
4404
+ } from "@tinycloud/sdk-core";
3790
4405
  import {
3791
4406
  DelegationManager as DelegationManager2,
3792
4407
  SharingService as SharingService2,
@@ -3822,6 +4437,7 @@ export {
3822
4437
  CapabilityKeyRegistryErrorCodes,
3823
4438
  DEFAULT_MANIFEST_SPACE2 as DEFAULT_MANIFEST_SPACE,
3824
4439
  DEFAULT_MANIFEST_VERSION,
4440
+ DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
3825
4441
  DataVaultService2 as DataVaultService,
3826
4442
  DatabaseHandle,
3827
4443
  DelegatedAccess,
@@ -3838,6 +4454,7 @@ export {
3838
4454
  PermissionNotInManifestError2 as PermissionNotInManifestError,
3839
4455
  PrefixedKVService,
3840
4456
  ProtocolMismatchError,
4457
+ SECRET_NAME_RE,
3841
4458
  SQLAction,
3842
4459
  SQLService3 as SQLService,
3843
4460
  SecretsService2 as SecretsService,
@@ -3851,11 +4468,13 @@ export {
3851
4468
  TinyCloud2 as TinyCloud,
3852
4469
  TinyCloudNode,
3853
4470
  UnsupportedFeatureError2 as UnsupportedFeatureError,
4471
+ VAULT_PERMISSION_SERVICE,
3854
4472
  VaultHeaders,
3855
4473
  VaultPublicSpaceKVActions,
3856
4474
  VersionCheckError,
3857
4475
  WasmKeyProvider,
3858
4476
  buildSpaceUri,
4477
+ canonicalizeSecretScope,
3859
4478
  checkNodeInfo2 as checkNodeInfo,
3860
4479
  composeManifestRequest2 as composeManifestRequest,
3861
4480
  createCapabilityKeyRegistry,
@@ -3866,13 +4485,17 @@ export {
3866
4485
  defaultSignStrategy,
3867
4486
  defaultSpaceCreationHandler,
3868
4487
  deserializeDelegation,
3869
- expandActionShortNames2 as expandActionShortNames,
4488
+ expandActionShortNames,
4489
+ expandPermissionEntries2 as expandPermissionEntries,
4490
+ expandPermissionEntry,
3870
4491
  isCapabilitySubset2 as isCapabilitySubset,
3871
4492
  loadManifest,
3872
4493
  makePublicSpaceId2 as makePublicSpaceId,
3873
4494
  parseExpiry2 as parseExpiry,
3874
4495
  parseSpaceUri,
3875
4496
  resolveManifest2 as resolveManifest,
4497
+ resolveSecretListPrefix2 as resolveSecretListPrefix,
4498
+ resolveSecretPath2 as resolveSecretPath,
3876
4499
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
3877
4500
  serializeDelegation,
3878
4501
  validateManifest