@tinycloud/node-sdk 2.2.0-beta.9 → 2.2.1-beta.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
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 };
@@ -1241,27 +1315,39 @@ function secretsError(code, message, cause) {
1241
1315
  };
1242
1316
  }
1243
1317
  function displayActionUrn(action) {
1244
- return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
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 kvActionUrn(action) {
1247
- return `tinycloud.kv/${action}`;
1329
+ function secretActionName(action) {
1330
+ return action;
1248
1331
  }
1249
- function secretPermissionEntries(name, action) {
1250
- return [
1251
- {
1252
- service: "tinycloud.vault",
1253
- space: SECRETS_SPACE,
1254
- path: `${SECRET_PREFIX}${name}`,
1255
- actions: [action === "put" ? "write" : "delete"],
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"],
1256
1347
  skipPrefix: true
1257
- }
1258
- ];
1259
- }
1260
- function secretResourcePath(base, name) {
1261
- return `${base}/${SECRET_PREFIX}${name}`;
1262
- }
1263
- function isSecretsSpace(space) {
1264
- return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1348
+ });
1349
+ }
1350
+ return entries;
1265
1351
  }
1266
1352
  var NodeSecretsService = class {
1267
1353
  constructor(config) {
@@ -1289,48 +1375,62 @@ var NodeSecretsService = class {
1289
1375
  this.shouldRestoreUnlock = false;
1290
1376
  this.service.lock();
1291
1377
  }
1292
- get(name) {
1293
- 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);
1294
1382
  }
1295
- async put(name, value) {
1296
- const permission = await this.ensureMutationPermission(name, "put");
1383
+ async put(name, value, options) {
1384
+ const permission = await this.ensurePermission(name, options, "put");
1297
1385
  if (!permission.ok) return permission;
1298
- return this.service.put(name, value);
1386
+ return options === void 0 ? this.service.put(name, value) : this.service.put(name, value, options);
1299
1387
  }
1300
- async delete(name) {
1301
- const permission = await this.ensureMutationPermission(name, "del");
1388
+ async delete(name, options) {
1389
+ const permission = await this.ensurePermission(name, options, "del");
1302
1390
  if (!permission.ok) return permission;
1303
- return this.service.delete(name);
1391
+ return options === void 0 ? this.service.delete(name) : this.service.delete(name, options);
1304
1392
  }
1305
- list() {
1306
- 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);
1307
1397
  }
1308
1398
  get service() {
1309
1399
  return this.config.getService();
1310
1400
  }
1311
- async ensureMutationPermission(name, action) {
1312
- 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) {
1313
1412
  return secretsError(
1314
1413
  ErrorCodes.INVALID_INPUT,
1315
- `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
1316
1416
  );
1317
1417
  }
1318
- if (this.hasMutationPermission(name, action)) {
1418
+ if (this.hasPermission(permissionEntries)) {
1319
1419
  return ok();
1320
1420
  }
1321
1421
  if (!this.config.canEscalate()) {
1322
1422
  return secretsError(
1323
1423
  ErrorCodes.PERMISSION_DENIED,
1324
- `Cannot autosign ${displayActionUrn(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.`
1325
1425
  );
1326
1426
  }
1327
1427
  try {
1328
- await this.config.grantPermissions(secretPermissionEntries(name, action));
1428
+ await this.config.grantPermissions(permissionEntries);
1329
1429
  return this.restoreUnlockAfterEscalation();
1330
1430
  } catch (error) {
1331
1431
  return secretsError(
1332
1432
  ErrorCodes.PERMISSION_DENIED,
1333
- error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
1433
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${target} failed.`,
1334
1434
  error instanceof Error ? error : void 0
1335
1435
  );
1336
1436
  }
@@ -1341,26 +1441,117 @@ var NodeSecretsService = class {
1341
1441
  }
1342
1442
  return this.service.unlock(this.unlockSigner);
1343
1443
  }
1344
- hasMutationPermission(name, action) {
1444
+ hasPermission(permissionEntries) {
1445
+ if (this.config.hasPermissions?.(permissionEntries)) {
1446
+ return true;
1447
+ }
1345
1448
  const manifest = this.config.getManifest();
1346
1449
  if (manifest === void 0) {
1347
1450
  return false;
1348
1451
  }
1349
1452
  const manifests = Array.isArray(manifest) ? manifest : [manifest];
1350
- const requiredAction = kvActionUrn(action);
1351
- return manifests.some((entry) => {
1352
- const resolved = resolveManifest(entry);
1353
- return ["keys", "vault"].every(
1354
- (base) => resolved.resources.some(
1355
- (resource) => resource.service === "tinycloud.kv" && isSecretsSpace(resource.space) && resource.path === secretResourcePath(base, name) && resource.actions.includes(requiredAction)
1356
- )
1357
- );
1358
- });
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
+ );
1359
1462
  }
1360
1463
  };
1361
1464
 
1362
1465
  // src/TinyCloudNode.ts
1363
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
+ }
1364
1555
  var _TinyCloudNode = class _TinyCloudNode {
1365
1556
  /**
1366
1557
  * Create a new TinyCloudNode instance.
@@ -1405,12 +1596,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1405
1596
  throw new Error("WASM binding does not support invokeAny");
1406
1597
  }
1407
1598
  const grant = this.findGrantForOperations(
1408
- entries.map((entry) => ({
1409
- spaceId: entry.spaceId,
1410
- service: this.invocationServiceName(entry.service),
1411
- path: entry.path,
1412
- action: entry.action
1413
- }))
1599
+ entries.flatMap((entry) => {
1600
+ const operation = this.operationFromInvokeAnyEntry(entry);
1601
+ return operation ? [operation] : [];
1602
+ })
1414
1603
  );
1415
1604
  return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1416
1605
  };
@@ -1503,7 +1692,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1503
1692
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
1504
1693
  domain: this.siweDomain,
1505
1694
  spacePrefix: config.prefix,
1506
- sessionExpirationMs: config.sessionExpirationMs ?? 60 * 60 * 1e3,
1695
+ sessionExpirationMs: config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1507
1696
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1508
1697
  tinycloudRegistryUrl: config.tinycloudRegistryUrl,
1509
1698
  tinycloudFallbackHosts: config.tinycloudFallbackHosts,
@@ -1638,6 +1827,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1638
1827
  this._duckdb = void 0;
1639
1828
  this._hooks = void 0;
1640
1829
  this._vault = void 0;
1830
+ this._encryption = void 0;
1641
1831
  this._baseSecrets = void 0;
1642
1832
  this._secrets = void 0;
1643
1833
  this._spaceService = void 0;
@@ -1646,6 +1836,9 @@ var _TinyCloudNode = class _TinyCloudNode {
1646
1836
  await this.tc.signIn(options);
1647
1837
  this.syncResolvedHostFromAuth();
1648
1838
  this.initializeServices();
1839
+ if (this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
1840
+ await this.ensureOwnedSpaceHosted(this.ownedSpaceId("secrets"));
1841
+ }
1649
1842
  await this.writeManifestRegistryRecords();
1650
1843
  this.notificationHandler.success("Successfully signed in");
1651
1844
  }
@@ -1728,6 +1921,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1728
1921
  this._duckdb = void 0;
1729
1922
  this._hooks = void 0;
1730
1923
  this._vault = void 0;
1924
+ this._encryption = void 0;
1731
1925
  this._baseSecrets = void 0;
1732
1926
  this._secrets = void 0;
1733
1927
  this._spaceService = void 0;
@@ -1769,6 +1963,33 @@ var _TinyCloudNode = class _TinyCloudNode {
1769
1963
  this._vault.initialize(this._serviceContext);
1770
1964
  this._serviceContext.registerService("vault", this._vault);
1771
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;
1772
1993
  }
1773
1994
  /**
1774
1995
  * Connect a wallet to upgrade from session-only mode to wallet mode.
@@ -1813,7 +2034,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1813
2034
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
1814
2035
  domain: this.siweDomain,
1815
2036
  spacePrefix: prefix,
1816
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
2037
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1817
2038
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1818
2039
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1819
2040
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
@@ -1857,7 +2078,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1857
2078
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
1858
2079
  domain: this.siweDomain,
1859
2080
  spacePrefix: prefix,
1860
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
2081
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1861
2082
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1862
2083
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1863
2084
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
@@ -1880,7 +2101,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1880
2101
  * @internal
1881
2102
  */
1882
2103
  initializeServices() {
1883
- const session = this.auth?.tinyCloudSession;
2104
+ const session = this.currentTinyCloudSession();
1884
2105
  if (!session) {
1885
2106
  return;
1886
2107
  }
@@ -1938,6 +2159,180 @@ var _TinyCloudNode = class _TinyCloudNode {
1938
2159
  }
1939
2160
  return kvService;
1940
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
+ wellKnown: {
2307
+ fetchWellKnown: async (principal, discoveryKey) => {
2308
+ if (!this._address || principal !== this.did) {
2309
+ return null;
2310
+ }
2311
+ if (!this.config.host) {
2312
+ return null;
2313
+ }
2314
+ const publicSpaceId = makePublicSpaceId(this._address, this._chainId);
2315
+ const result = await TinyCloud.readPublicSpace(this.config.host, publicSpaceId, discoveryKey);
2316
+ if (!result.ok) {
2317
+ return null;
2318
+ }
2319
+ const body = result.data;
2320
+ return "descriptor" in body && body.descriptor ? body.descriptor : body;
2321
+ }
2322
+ }
2323
+ });
2324
+ }
2325
+ getEncryptionService() {
2326
+ if (!this._serviceContext) {
2327
+ throw new Error("Not signed in. Call signIn() first.");
2328
+ }
2329
+ if (!this._encryption) {
2330
+ this._encryption = this.createEncryptionService();
2331
+ this._encryption.initialize(this._serviceContext);
2332
+ this._serviceContext.registerService("encryption", this._encryption);
2333
+ }
2334
+ return this._encryption;
2335
+ }
1941
2336
  createVaultService(spaceId, kv) {
1942
2337
  const wasm = this.wasmBindings;
1943
2338
  const vaultCrypto = createVaultCrypto({
@@ -1953,6 +2348,13 @@ var _TinyCloudNode = class _TinyCloudNode {
1953
2348
  return new DataVaultService({
1954
2349
  spaceId,
1955
2350
  crypto: vaultCrypto,
2351
+ encryption: {
2352
+ networkId: this.getDefaultEncryptionNetworkId(),
2353
+ service: this.getEncryptionService(),
2354
+ decryptCapabilityProof: () => ({
2355
+ proofs: [this.requireServiceSession().delegationCid]
2356
+ })
2357
+ },
1956
2358
  tc: {
1957
2359
  kv,
1958
2360
  ensurePublicSpace: async () => {
@@ -2133,7 +2535,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2133
2535
  * @internal
2134
2536
  */
2135
2537
  getSessionExpiry() {
2136
- const expirationMs = this.config.sessionExpirationMs ?? 60 * 60 * 1e3;
2538
+ const expirationMs = this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS;
2137
2539
  return new Date(Date.now() + expirationMs);
2138
2540
  }
2139
2541
  /**
@@ -2190,7 +2592,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2190
2592
  if (!this.signer) {
2191
2593
  return void 0;
2192
2594
  }
2193
- const session = this.auth?.tinyCloudSession;
2595
+ const session = this.currentTinyCloudSession();
2194
2596
  if (!session) {
2195
2597
  return void 0;
2196
2598
  }
@@ -2290,6 +2692,34 @@ var _TinyCloudNode = class _TinyCloudNode {
2290
2692
  }
2291
2693
  return this._sql;
2292
2694
  }
2695
+ /**
2696
+ * Get an SQL service scoped to a specific space.
2697
+ *
2698
+ * Mirrors {@link SpaceService}'s per-space KV factory: clones the active
2699
+ * service context and overrides its session's spaceId so that subsequent
2700
+ * `sql/<dbName>/<action>` invocations route to that space. Useful when
2701
+ * the caller already holds a delegation covering the target space (e.g.
2702
+ * via {@link grantRuntimePermissions} or {@link useRuntimeDelegation})
2703
+ * but the SDK's per-space SQL surface isn't otherwise exposed.
2704
+ *
2705
+ * Does NOT auto-create the space.
2706
+ *
2707
+ * @param spaceId - Full space URI (`tinycloud:pkh:eip155:<chain>:<addr>:<name>`).
2708
+ */
2709
+ sqlForSpace(spaceId) {
2710
+ if (!this._serviceContext || !this._serviceContext.session) {
2711
+ throw new Error("Not signed in. Call signIn() first.");
2712
+ }
2713
+ const sql = new SQLService2({});
2714
+ const spaceScopedContext = new ServiceContext2({
2715
+ invoke: this._serviceContext.invoke,
2716
+ fetch: this._serviceContext.fetch,
2717
+ hosts: this._serviceContext.hosts
2718
+ });
2719
+ spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
2720
+ sql.initialize(spaceScopedContext);
2721
+ return sql;
2722
+ }
2293
2723
  /**
2294
2724
  * DuckDB database operations on this user's space.
2295
2725
  */
@@ -2313,6 +2743,79 @@ var _TinyCloudNode = class _TinyCloudNode {
2313
2743
  }
2314
2744
  return this._vault;
2315
2745
  }
2746
+ /**
2747
+ * Network-scoped encryption/decrypt service.
2748
+ */
2749
+ get encryption() {
2750
+ return this.getEncryptionService();
2751
+ }
2752
+ async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
2753
+ const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
2754
+ const response = await fetch(
2755
+ `${this.config.host}/encryption/networks/${encodeURIComponent(networkId)}`
2756
+ );
2757
+ if (response.status === 404) {
2758
+ return null;
2759
+ }
2760
+ if (!response.ok) {
2761
+ throw new Error(
2762
+ `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
2763
+ );
2764
+ }
2765
+ const body = await response.json();
2766
+ return "descriptor" in body && body.descriptor ? body.descriptor : body;
2767
+ }
2768
+ async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
2769
+ const targetNode = await this.fetchNodeId();
2770
+ const principal = this.did;
2771
+ const networkId = this.getDefaultEncryptionNetworkId(name);
2772
+ const body = {
2773
+ name,
2774
+ principal,
2775
+ threshold: { n: 1, t: 1 }
2776
+ };
2777
+ const crypto = this.createEncryptionCrypto();
2778
+ const facts = {
2779
+ type: NETWORK_ADMIN_TYPE,
2780
+ targetNode,
2781
+ networkId,
2782
+ bodyHash: canonicalHashHex(
2783
+ crypto.sha256,
2784
+ body
2785
+ ),
2786
+ action: NETWORK_CREATE_ACTION
2787
+ };
2788
+ const signed = await this.signRawNetworkAuthorization({
2789
+ targetNode,
2790
+ networkId,
2791
+ action: NETWORK_CREATE_ACTION,
2792
+ facts
2793
+ });
2794
+ const response = await fetch(`${this.config.host}/encryption/networks`, {
2795
+ method: "POST",
2796
+ headers: {
2797
+ Authorization: signed.authorization,
2798
+ "Content-Type": "application/json"
2799
+ },
2800
+ body: canonicalizeEncryptionJson(
2801
+ body
2802
+ )
2803
+ });
2804
+ if (!response.ok) {
2805
+ throw new Error(
2806
+ `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
2807
+ );
2808
+ }
2809
+ const created = await response.json();
2810
+ return created.descriptor;
2811
+ }
2812
+ async ensureEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
2813
+ const existing = await this.getEncryptionNetwork(name);
2814
+ if (existing) {
2815
+ return existing;
2816
+ }
2817
+ return this.createEncryptionNetwork(name);
2818
+ }
2316
2819
  /**
2317
2820
  * App-facing secrets API backed by the `secrets` space vault.
2318
2821
  */
@@ -2324,8 +2827,10 @@ var _TinyCloudNode = class _TinyCloudNode {
2324
2827
  this._secrets = new NodeSecretsService({
2325
2828
  getService: () => this.getBaseSecrets(),
2326
2829
  getManifest: () => this.manifest,
2830
+ hasPermissions: (permissions) => this.hasRuntimePermissions(permissions),
2327
2831
  grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2328
2832
  canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2833
+ getEncryptionNetworkId: () => this.getDefaultEncryptionNetworkId(),
2329
2834
  getUnlockSigner: () => this.signer ?? void 0
2330
2835
  });
2331
2836
  }
@@ -2415,7 +2920,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2415
2920
  * every requested permission.
2416
2921
  */
2417
2922
  hasRuntimePermissions(permissions) {
2418
- const session = this.auth?.tinyCloudSession;
2923
+ const session = this.currentTinyCloudSession();
2419
2924
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2420
2925
  return false;
2421
2926
  }
@@ -2435,7 +2940,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2435
2940
  if (permissions === void 0) {
2436
2941
  return this.runtimePermissionGrants.map((grant) => grant.delegation);
2437
2942
  }
2438
- const session = this.auth?.tinyCloudSession;
2943
+ const session = this.currentTinyCloudSession();
2439
2944
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2440
2945
  return [];
2441
2946
  }
@@ -2449,7 +2954,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2449
2954
  * matching service calls and downstream `delegateTo()` calls can use it.
2450
2955
  */
2451
2956
  async useRuntimeDelegation(delegation) {
2452
- const session = this.auth?.tinyCloudSession;
2957
+ const session = this.currentTinyCloudSession();
2453
2958
  if (!session) {
2454
2959
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2455
2960
  }
@@ -2488,7 +2993,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2488
2993
  if (!Array.isArray(permissions) || permissions.length === 0) {
2489
2994
  throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2490
2995
  }
2491
- const session = this.auth?.tinyCloudSession;
2996
+ const session = this.currentTinyCloudSession();
2492
2997
  if (!session) {
2493
2998
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2494
2999
  }
@@ -2512,13 +3017,22 @@ var _TinyCloudNode = class _TinyCloudNode {
2512
3017
  "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2513
3018
  );
2514
3019
  }
3020
+ const rawEntries = expanded.filter(
3021
+ (entry) => this.isEncryptionPermissionEntry(entry)
3022
+ );
3023
+ const spaceEntries = expanded.filter(
3024
+ (entry) => !this.isEncryptionPermissionEntry(entry)
3025
+ );
2515
3026
  const bySpace = /* @__PURE__ */ new Map();
2516
- for (const entry of expanded) {
3027
+ for (const entry of spaceEntries) {
2517
3028
  const spaceId = this.resolvePermissionSpace(entry.space, session);
2518
3029
  const current = bySpace.get(spaceId) ?? [];
2519
3030
  current.push(entry);
2520
3031
  bySpace.set(spaceId, current);
2521
3032
  }
3033
+ if (bySpace.size === 0 && rawEntries.length > 0) {
3034
+ bySpace.set(session.spaceId, []);
3035
+ }
2522
3036
  const now = /* @__PURE__ */ new Date();
2523
3037
  const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2524
3038
  let expiresAt = new Date(now.getTime() + requestedExpiryMs);
@@ -2526,10 +3040,17 @@ var _TinyCloudNode = class _TinyCloudNode {
2526
3040
  expiresAt = sessionExpiry;
2527
3041
  }
2528
3042
  const delegations = [];
3043
+ let rawEntriesAttached = false;
2529
3044
  for (const [spaceId, entries] of bySpace) {
3045
+ const rawForDelegation = !rawEntriesAttached ? rawEntries : [];
3046
+ if (rawForDelegation.length > 0) {
3047
+ rawEntriesAttached = true;
3048
+ }
3049
+ const delegatedEntries = [...entries, ...rawForDelegation];
2530
3050
  const abilities = this.permissionsToAbilities(entries);
2531
3051
  const prepared = this.wasmBindings.prepareSession({
2532
3052
  abilities,
3053
+ ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
2533
3054
  address: this.wasmBindings.ensureEip55(session.address),
2534
3055
  chainId: session.chainId,
2535
3056
  domain: this.siweDomain,
@@ -2554,7 +3075,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2554
3075
  }
2555
3076
  const delegation = this.runtimeDelegationFromSession(
2556
3077
  delegatedSession,
2557
- entries,
3078
+ delegatedEntries,
2558
3079
  spaceId,
2559
3080
  session,
2560
3081
  expiresAt
@@ -2568,7 +3089,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2568
3089
  jwk: session.jwk
2569
3090
  },
2570
3091
  delegation,
2571
- operations: this.permissionOperations(entries, spaceId),
3092
+ operations: this.permissionOperations(delegatedEntries, spaceId),
2572
3093
  expiresAt
2573
3094
  });
2574
3095
  delegations.push(delegation);
@@ -2716,7 +3237,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2716
3237
  ];
2717
3238
  const abilities = { kv: { "": kvActions } };
2718
3239
  const now = /* @__PURE__ */ new Date();
2719
- const expiryMs = 60 * 60 * 1e3;
3240
+ const expiryMs = EXPIRY3.EPHEMERAL_MS;
2720
3241
  const expirationTime = new Date(now.getTime() + expiryMs);
2721
3242
  const prepared = this.wasmBindings.prepareSession({
2722
3243
  abilities,
@@ -2886,7 +3407,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2886
3407
  * `forceWalletSign` is not set.
2887
3408
  */
2888
3409
  async delegateTo(did, permissions, options) {
2889
- const session = this.auth?.tinyCloudSession;
3410
+ const session = this.currentTinyCloudSession();
2890
3411
  if (!session) {
2891
3412
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2892
3413
  }
@@ -3000,11 +3521,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3000
3521
  * the current session; we build one multi-resource abilities map
3001
3522
  * and emit one signed UCAN covering them all.
3002
3523
  *
3003
- * All entries must share the same target space (the UCAN is
3004
- * scoped to a single space). If they don't, this throws mixing
3005
- * spaces in a single delegation is not supported by the underlying
3006
- * Rust create_delegation call and the resulting UCAN would be
3007
- * under-specified.
3524
+ * Non-encryption entries must share the same target space. Encryption
3525
+ * entries are raw network URNs and do not participate in space grouping.
3008
3526
  *
3009
3527
  * @internal
3010
3528
  */
@@ -3016,15 +3534,18 @@ var _TinyCloudNode = class _TinyCloudNode {
3016
3534
  }
3017
3535
  const resolvedSpaces = /* @__PURE__ */ new Set();
3018
3536
  for (const entry of entries) {
3537
+ if (this.isEncryptionPermissionEntry(entry)) {
3538
+ continue;
3539
+ }
3019
3540
  const spaceId2 = this.resolvePermissionSpace(entry.space, session);
3020
3541
  resolvedSpaces.add(spaceId2);
3021
3542
  }
3022
- if (resolvedSpaces.size !== 1) {
3543
+ if (resolvedSpaces.size > 1) {
3023
3544
  throw new Error(
3024
3545
  `delegateTo: all permission entries must target the same space, got ${resolvedSpaces.size}: ${JSON.stringify([...resolvedSpaces])}`
3025
3546
  );
3026
3547
  }
3027
- const spaceId = [...resolvedSpaces][0];
3548
+ const spaceId = resolvedSpaces.size === 1 ? [...resolvedSpaces][0] : session.spaceId;
3028
3549
  const abilities = {};
3029
3550
  for (const entry of entries) {
3030
3551
  const shortService = SERVICE_LONG_TO_SHORT[entry.service];
@@ -3171,11 +3692,32 @@ var _TinyCloudNode = class _TinyCloudNode {
3171
3692
  }
3172
3693
  return abilities;
3173
3694
  }
3695
+ isEncryptionPermissionEntry(entry) {
3696
+ return entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.path.startsWith("urn:tinycloud:encryption:");
3697
+ }
3698
+ permissionsToRawAbilities(entries) {
3699
+ const rawAbilities = {};
3700
+ for (const entry of entries) {
3701
+ if (!this.isEncryptionPermissionEntry(entry)) {
3702
+ continue;
3703
+ }
3704
+ const existing = rawAbilities[entry.path] ?? [];
3705
+ const seen = new Set(existing);
3706
+ for (const action of entry.actions) {
3707
+ if (!seen.has(action)) {
3708
+ existing.push(action);
3709
+ seen.add(action);
3710
+ }
3711
+ }
3712
+ rawAbilities[entry.path] = existing;
3713
+ }
3714
+ return rawAbilities;
3715
+ }
3174
3716
  permissionOperations(entries, spaceId) {
3175
3717
  return entries.flatMap((entry) => {
3176
3718
  const service = this.shortServiceName(entry.service);
3177
3719
  return entry.actions.map((action) => ({
3178
- spaceId,
3720
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
3179
3721
  service,
3180
3722
  path: entry.path,
3181
3723
  action
@@ -3198,7 +3740,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3198
3740
  const spaceId = this.resolvePermissionSpace(entry.space, session);
3199
3741
  const service = this.shortServiceName(entry.service);
3200
3742
  return entry.actions.map((action) => ({
3201
- spaceId,
3743
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
3202
3744
  service,
3203
3745
  path: entry.path,
3204
3746
  action
@@ -3255,24 +3797,40 @@ var _TinyCloudNode = class _TinyCloudNode {
3255
3797
  expiresAt: delegation.expiry
3256
3798
  };
3257
3799
  }
3800
+ installRuntimeGrantFromServiceSession(delegation, session, expiresAt) {
3801
+ const operations = this.operationsFromDelegation(delegation);
3802
+ if (operations.length === 0) {
3803
+ return;
3804
+ }
3805
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3806
+ (grant) => grant.delegation.cid !== delegation.cid && grant.session.delegationCid !== session.delegationCid
3807
+ );
3808
+ this.runtimePermissionGrants.push({
3809
+ session,
3810
+ delegation,
3811
+ operations,
3812
+ expiresAt
3813
+ });
3814
+ }
3258
3815
  delegatedResourcesForEntries(entries, spaceId) {
3259
3816
  return entries.map((entry) => ({
3260
3817
  service: this.shortServiceName(entry.service),
3261
- space: spaceId,
3818
+ space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
3262
3819
  path: entry.path,
3263
3820
  actions: [...entry.actions]
3264
3821
  }));
3265
3822
  }
3266
3823
  operationsFromDelegation(delegation) {
3267
3824
  const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3268
- return resources.flatMap(
3269
- (resource) => resource.actions.map((action) => ({
3270
- spaceId: resource.space,
3271
- service: this.invocationServiceName(resource.service),
3825
+ return resources.flatMap((resource) => {
3826
+ const service = this.invocationServiceName(resource.service);
3827
+ return resource.actions.map((action) => ({
3828
+ ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
3829
+ service,
3272
3830
  path: resource.path,
3273
3831
  action
3274
- }))
3275
- );
3832
+ }));
3833
+ });
3276
3834
  }
3277
3835
  flatDelegationResources(delegation) {
3278
3836
  const byService = /* @__PURE__ */ new Map();
@@ -3321,7 +3879,13 @@ var _TinyCloudNode = class _TinyCloudNode {
3321
3879
  );
3322
3880
  }
3323
3881
  operationCovers(granted, requested) {
3324
- return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3882
+ if (granted.service !== requested.service || !this.actionContains(granted.action, requested.action)) {
3883
+ return false;
3884
+ }
3885
+ if (granted.resource !== void 0 || requested.resource !== void 0) {
3886
+ return granted.resource !== void 0 && requested.resource !== void 0 && granted.resource === requested.resource && this.pathContains(granted.path, requested.path);
3887
+ }
3888
+ return granted.spaceId !== void 0 && requested.spaceId !== void 0 && granted.spaceId === requested.spaceId && this.pathContains(granted.path, requested.path);
3325
3889
  }
3326
3890
  actionContains(grantedAction, requestedAction) {
3327
3891
  if (grantedAction === requestedAction) {
@@ -3336,6 +3900,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3336
3900
  invocationServiceName(service) {
3337
3901
  return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3338
3902
  }
3903
+ isEncryptionNetworkOperation(service, path) {
3904
+ return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
3905
+ }
3906
+ operationFromInvokeAnyEntry(entry) {
3907
+ const service = this.invocationServiceName(entry.service);
3908
+ if (typeof entry.resource === "string") {
3909
+ return {
3910
+ resource: entry.resource,
3911
+ service,
3912
+ path: entry.path,
3913
+ action: entry.action
3914
+ };
3915
+ }
3916
+ if (this.isEncryptionNetworkOperation(service, entry.path)) {
3917
+ return {
3918
+ resource: entry.path,
3919
+ service,
3920
+ path: entry.path,
3921
+ action: entry.action
3922
+ };
3923
+ }
3924
+ if (typeof entry.spaceId === "string") {
3925
+ return {
3926
+ spaceId: entry.spaceId,
3927
+ service,
3928
+ path: entry.path,
3929
+ action: entry.action
3930
+ };
3931
+ }
3932
+ return void 0;
3933
+ }
3339
3934
  pathContains(grantedPath, requestedPath) {
3340
3935
  if (grantedPath === "" || grantedPath === "/") {
3341
3936
  return true;
@@ -3574,6 +4169,17 @@ var _TinyCloudNode = class _TinyCloudNode {
3574
4169
  // Not used in session-only mode
3575
4170
  };
3576
4171
  this.trackReceivedDelegation(delegation, this.sessionKeyJwk);
4172
+ this.installRuntimeGrantFromServiceSession(
4173
+ delegation,
4174
+ {
4175
+ delegationHeader: session2.delegationHeader,
4176
+ delegationCid: session2.delegationCid,
4177
+ spaceId: session2.spaceId,
4178
+ verificationMethod: session2.verificationMethod,
4179
+ jwk: session2.jwk
4180
+ },
4181
+ delegation.expiry
4182
+ );
3577
4183
  return new DelegatedAccess(session2, delegation, targetHost, this.wasmBindings.invoke);
3578
4184
  }
3579
4185
  const mySession = this.auth?.tinyCloudSession;
@@ -3585,6 +4191,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3585
4191
  const kvActions = delegation.actions.filter((a) => a.startsWith("tinycloud.kv/"));
3586
4192
  const sqlActions = delegation.actions.filter((a) => a.startsWith("tinycloud.sql/"));
3587
4193
  const duckdbActions = delegation.actions.filter((a) => a.startsWith("tinycloud.duckdb/"));
4194
+ const encryptionActions = delegation.actions.filter(
4195
+ (a) => a.startsWith("tinycloud.encryption/")
4196
+ );
4197
+ const rawAbilities = {};
3588
4198
  if (kvActions.length > 0) {
3589
4199
  abilities.kv = { [delegation.path]: kvActions };
3590
4200
  }
@@ -3594,6 +4204,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3594
4204
  if (duckdbActions.length > 0) {
3595
4205
  abilities.duckdb = { [delegation.path]: duckdbActions };
3596
4206
  }
4207
+ if (encryptionActions.length > 0 && delegation.path.startsWith("urn:tinycloud:encryption:")) {
4208
+ rawAbilities[delegation.path] = encryptionActions;
4209
+ }
3597
4210
  const now = /* @__PURE__ */ new Date();
3598
4211
  const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
3599
4212
  const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
@@ -3606,7 +4219,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3606
4219
  expirationTime: expirationTime.toISOString(),
3607
4220
  spaceId: delegation.spaceId,
3608
4221
  jwk,
3609
- parents: [delegation.cid]
4222
+ parents: [delegation.cid],
4223
+ ...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
3610
4224
  });
3611
4225
  const signature = await this.signer.signMessage(prepared.siwe);
3612
4226
  const invokerSession = this.wasmBindings.completeSessionSetup({
@@ -3633,6 +4247,17 @@ var _TinyCloudNode = class _TinyCloudNode {
3633
4247
  signature
3634
4248
  };
3635
4249
  this.trackReceivedDelegation(delegation, jwk);
4250
+ this.installRuntimeGrantFromServiceSession(
4251
+ delegation,
4252
+ {
4253
+ delegationHeader: session.delegationHeader,
4254
+ delegationCid: session.delegationCid,
4255
+ spaceId: session.spaceId,
4256
+ verificationMethod: session.verificationMethod,
4257
+ jwk: session.jwk
4258
+ },
4259
+ expirationTime
4260
+ );
3636
4261
  return new DelegatedAccess(session, delegation, targetHost, this.wasmBindings.invoke);
3637
4262
  }
3638
4263
  /**
@@ -3753,7 +4378,7 @@ import {
3753
4378
  loadManifest,
3754
4379
  isCapabilitySubset as isCapabilitySubset2,
3755
4380
  expandActionShortNames,
3756
- expandPermissionEntries,
4381
+ expandPermissionEntries as expandPermissionEntries2,
3757
4382
  expandPermissionEntry,
3758
4383
  parseExpiry as parseExpiry2,
3759
4384
  resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
@@ -3776,10 +4401,24 @@ function deserializeDelegation(data) {
3776
4401
  }
3777
4402
 
3778
4403
  // src/core.ts
3779
- import { KVService as KVService3, PrefixedKVService } from "@tinycloud/sdk-core";
4404
+ import {
4405
+ DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
4406
+ KVService as KVService3,
4407
+ PrefixedKVService
4408
+ } from "@tinycloud/sdk-core";
3780
4409
  import { SQLService as SQLService3, SQLAction, DatabaseHandle } from "@tinycloud/sdk-core";
3781
4410
  import { DuckDbService as DuckDbService3, DuckDbDatabaseHandle, DuckDbAction } from "@tinycloud/sdk-core";
3782
- import { DataVaultService as DataVaultService2, VaultHeaders, VaultPublicSpaceKVActions, createVaultCrypto as createVaultCrypto2, SecretsService as SecretsService2 } from "@tinycloud/sdk-core";
4411
+ import {
4412
+ DataVaultService as DataVaultService2,
4413
+ VaultHeaders,
4414
+ VaultPublicSpaceKVActions,
4415
+ createVaultCrypto as createVaultCrypto2,
4416
+ SecretsService as SecretsService2,
4417
+ SECRET_NAME_RE,
4418
+ canonicalizeSecretScope,
4419
+ resolveSecretListPrefix as resolveSecretListPrefix2,
4420
+ resolveSecretPath as resolveSecretPath2
4421
+ } from "@tinycloud/sdk-core";
3783
4422
  import {
3784
4423
  DelegationManager as DelegationManager2,
3785
4424
  SharingService as SharingService2,
@@ -3815,6 +4454,7 @@ export {
3815
4454
  CapabilityKeyRegistryErrorCodes,
3816
4455
  DEFAULT_MANIFEST_SPACE2 as DEFAULT_MANIFEST_SPACE,
3817
4456
  DEFAULT_MANIFEST_VERSION,
4457
+ DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
3818
4458
  DataVaultService2 as DataVaultService,
3819
4459
  DatabaseHandle,
3820
4460
  DelegatedAccess,
@@ -3831,6 +4471,7 @@ export {
3831
4471
  PermissionNotInManifestError2 as PermissionNotInManifestError,
3832
4472
  PrefixedKVService,
3833
4473
  ProtocolMismatchError,
4474
+ SECRET_NAME_RE,
3834
4475
  SQLAction,
3835
4476
  SQLService3 as SQLService,
3836
4477
  SecretsService2 as SecretsService,
@@ -3850,6 +4491,7 @@ export {
3850
4491
  VersionCheckError,
3851
4492
  WasmKeyProvider,
3852
4493
  buildSpaceUri,
4494
+ canonicalizeSecretScope,
3853
4495
  checkNodeInfo2 as checkNodeInfo,
3854
4496
  composeManifestRequest2 as composeManifestRequest,
3855
4497
  createCapabilityKeyRegistry,
@@ -3861,7 +4503,7 @@ export {
3861
4503
  defaultSpaceCreationHandler,
3862
4504
  deserializeDelegation,
3863
4505
  expandActionShortNames,
3864
- expandPermissionEntries,
4506
+ expandPermissionEntries2 as expandPermissionEntries,
3865
4507
  expandPermissionEntry,
3866
4508
  isCapabilitySubset2 as isCapabilitySubset,
3867
4509
  loadManifest,
@@ -3869,6 +4511,8 @@ export {
3869
4511
  parseExpiry2 as parseExpiry,
3870
4512
  parseSpaceUri,
3871
4513
  resolveManifest2 as resolveManifest,
4514
+ resolveSecretListPrefix2 as resolveSecretListPrefix,
4515
+ resolveSecretPath2 as resolveSecretPath,
3872
4516
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
3873
4517
  serializeDelegation,
3874
4518
  validateManifest