@tinycloud/node-sdk 2.2.0-beta.9 → 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
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,163 @@ 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
+ });
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
+ }
1941
2319
  createVaultService(spaceId, kv) {
1942
2320
  const wasm = this.wasmBindings;
1943
2321
  const vaultCrypto = createVaultCrypto({
@@ -1953,6 +2331,13 @@ var _TinyCloudNode = class _TinyCloudNode {
1953
2331
  return new DataVaultService({
1954
2332
  spaceId,
1955
2333
  crypto: vaultCrypto,
2334
+ encryption: {
2335
+ networkId: this.getDefaultEncryptionNetworkId(),
2336
+ service: this.getEncryptionService(),
2337
+ decryptCapabilityProof: () => ({
2338
+ proofs: [this.requireServiceSession().delegationCid]
2339
+ })
2340
+ },
1956
2341
  tc: {
1957
2342
  kv,
1958
2343
  ensurePublicSpace: async () => {
@@ -2133,7 +2518,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2133
2518
  * @internal
2134
2519
  */
2135
2520
  getSessionExpiry() {
2136
- const expirationMs = this.config.sessionExpirationMs ?? 60 * 60 * 1e3;
2521
+ const expirationMs = this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS;
2137
2522
  return new Date(Date.now() + expirationMs);
2138
2523
  }
2139
2524
  /**
@@ -2190,7 +2575,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2190
2575
  if (!this.signer) {
2191
2576
  return void 0;
2192
2577
  }
2193
- const session = this.auth?.tinyCloudSession;
2578
+ const session = this.currentTinyCloudSession();
2194
2579
  if (!session) {
2195
2580
  return void 0;
2196
2581
  }
@@ -2290,6 +2675,34 @@ var _TinyCloudNode = class _TinyCloudNode {
2290
2675
  }
2291
2676
  return this._sql;
2292
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
+ }
2293
2706
  /**
2294
2707
  * DuckDB database operations on this user's space.
2295
2708
  */
@@ -2313,6 +2726,79 @@ var _TinyCloudNode = class _TinyCloudNode {
2313
2726
  }
2314
2727
  return this._vault;
2315
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
+ }
2316
2802
  /**
2317
2803
  * App-facing secrets API backed by the `secrets` space vault.
2318
2804
  */
@@ -2324,8 +2810,10 @@ var _TinyCloudNode = class _TinyCloudNode {
2324
2810
  this._secrets = new NodeSecretsService({
2325
2811
  getService: () => this.getBaseSecrets(),
2326
2812
  getManifest: () => this.manifest,
2813
+ hasPermissions: (permissions) => this.hasRuntimePermissions(permissions),
2327
2814
  grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2328
2815
  canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2816
+ getEncryptionNetworkId: () => this.getDefaultEncryptionNetworkId(),
2329
2817
  getUnlockSigner: () => this.signer ?? void 0
2330
2818
  });
2331
2819
  }
@@ -2415,7 +2903,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2415
2903
  * every requested permission.
2416
2904
  */
2417
2905
  hasRuntimePermissions(permissions) {
2418
- const session = this.auth?.tinyCloudSession;
2906
+ const session = this.currentTinyCloudSession();
2419
2907
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2420
2908
  return false;
2421
2909
  }
@@ -2435,7 +2923,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2435
2923
  if (permissions === void 0) {
2436
2924
  return this.runtimePermissionGrants.map((grant) => grant.delegation);
2437
2925
  }
2438
- const session = this.auth?.tinyCloudSession;
2926
+ const session = this.currentTinyCloudSession();
2439
2927
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2440
2928
  return [];
2441
2929
  }
@@ -2449,7 +2937,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2449
2937
  * matching service calls and downstream `delegateTo()` calls can use it.
2450
2938
  */
2451
2939
  async useRuntimeDelegation(delegation) {
2452
- const session = this.auth?.tinyCloudSession;
2940
+ const session = this.currentTinyCloudSession();
2453
2941
  if (!session) {
2454
2942
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2455
2943
  }
@@ -2488,7 +2976,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2488
2976
  if (!Array.isArray(permissions) || permissions.length === 0) {
2489
2977
  throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2490
2978
  }
2491
- const session = this.auth?.tinyCloudSession;
2979
+ const session = this.currentTinyCloudSession();
2492
2980
  if (!session) {
2493
2981
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2494
2982
  }
@@ -2512,13 +3000,22 @@ var _TinyCloudNode = class _TinyCloudNode {
2512
3000
  "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2513
3001
  );
2514
3002
  }
3003
+ const rawEntries = expanded.filter(
3004
+ (entry) => this.isEncryptionPermissionEntry(entry)
3005
+ );
3006
+ const spaceEntries = expanded.filter(
3007
+ (entry) => !this.isEncryptionPermissionEntry(entry)
3008
+ );
2515
3009
  const bySpace = /* @__PURE__ */ new Map();
2516
- for (const entry of expanded) {
3010
+ for (const entry of spaceEntries) {
2517
3011
  const spaceId = this.resolvePermissionSpace(entry.space, session);
2518
3012
  const current = bySpace.get(spaceId) ?? [];
2519
3013
  current.push(entry);
2520
3014
  bySpace.set(spaceId, current);
2521
3015
  }
3016
+ if (bySpace.size === 0 && rawEntries.length > 0) {
3017
+ bySpace.set(session.spaceId, []);
3018
+ }
2522
3019
  const now = /* @__PURE__ */ new Date();
2523
3020
  const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2524
3021
  let expiresAt = new Date(now.getTime() + requestedExpiryMs);
@@ -2526,10 +3023,17 @@ var _TinyCloudNode = class _TinyCloudNode {
2526
3023
  expiresAt = sessionExpiry;
2527
3024
  }
2528
3025
  const delegations = [];
3026
+ let rawEntriesAttached = false;
2529
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];
2530
3033
  const abilities = this.permissionsToAbilities(entries);
2531
3034
  const prepared = this.wasmBindings.prepareSession({
2532
3035
  abilities,
3036
+ ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
2533
3037
  address: this.wasmBindings.ensureEip55(session.address),
2534
3038
  chainId: session.chainId,
2535
3039
  domain: this.siweDomain,
@@ -2554,7 +3058,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2554
3058
  }
2555
3059
  const delegation = this.runtimeDelegationFromSession(
2556
3060
  delegatedSession,
2557
- entries,
3061
+ delegatedEntries,
2558
3062
  spaceId,
2559
3063
  session,
2560
3064
  expiresAt
@@ -2568,7 +3072,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2568
3072
  jwk: session.jwk
2569
3073
  },
2570
3074
  delegation,
2571
- operations: this.permissionOperations(entries, spaceId),
3075
+ operations: this.permissionOperations(delegatedEntries, spaceId),
2572
3076
  expiresAt
2573
3077
  });
2574
3078
  delegations.push(delegation);
@@ -2716,7 +3220,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2716
3220
  ];
2717
3221
  const abilities = { kv: { "": kvActions } };
2718
3222
  const now = /* @__PURE__ */ new Date();
2719
- const expiryMs = 60 * 60 * 1e3;
3223
+ const expiryMs = EXPIRY3.EPHEMERAL_MS;
2720
3224
  const expirationTime = new Date(now.getTime() + expiryMs);
2721
3225
  const prepared = this.wasmBindings.prepareSession({
2722
3226
  abilities,
@@ -2886,7 +3390,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2886
3390
  * `forceWalletSign` is not set.
2887
3391
  */
2888
3392
  async delegateTo(did, permissions, options) {
2889
- const session = this.auth?.tinyCloudSession;
3393
+ const session = this.currentTinyCloudSession();
2890
3394
  if (!session) {
2891
3395
  throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2892
3396
  }
@@ -3000,11 +3504,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3000
3504
  * the current session; we build one multi-resource abilities map
3001
3505
  * and emit one signed UCAN covering them all.
3002
3506
  *
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.
3507
+ * Non-encryption entries must share the same target space. Encryption
3508
+ * entries are raw network URNs and do not participate in space grouping.
3008
3509
  *
3009
3510
  * @internal
3010
3511
  */
@@ -3016,15 +3517,18 @@ var _TinyCloudNode = class _TinyCloudNode {
3016
3517
  }
3017
3518
  const resolvedSpaces = /* @__PURE__ */ new Set();
3018
3519
  for (const entry of entries) {
3520
+ if (this.isEncryptionPermissionEntry(entry)) {
3521
+ continue;
3522
+ }
3019
3523
  const spaceId2 = this.resolvePermissionSpace(entry.space, session);
3020
3524
  resolvedSpaces.add(spaceId2);
3021
3525
  }
3022
- if (resolvedSpaces.size !== 1) {
3526
+ if (resolvedSpaces.size > 1) {
3023
3527
  throw new Error(
3024
3528
  `delegateTo: all permission entries must target the same space, got ${resolvedSpaces.size}: ${JSON.stringify([...resolvedSpaces])}`
3025
3529
  );
3026
3530
  }
3027
- const spaceId = [...resolvedSpaces][0];
3531
+ const spaceId = resolvedSpaces.size === 1 ? [...resolvedSpaces][0] : session.spaceId;
3028
3532
  const abilities = {};
3029
3533
  for (const entry of entries) {
3030
3534
  const shortService = SERVICE_LONG_TO_SHORT[entry.service];
@@ -3171,11 +3675,32 @@ var _TinyCloudNode = class _TinyCloudNode {
3171
3675
  }
3172
3676
  return abilities;
3173
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
+ }
3174
3699
  permissionOperations(entries, spaceId) {
3175
3700
  return entries.flatMap((entry) => {
3176
3701
  const service = this.shortServiceName(entry.service);
3177
3702
  return entry.actions.map((action) => ({
3178
- spaceId,
3703
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
3179
3704
  service,
3180
3705
  path: entry.path,
3181
3706
  action
@@ -3198,7 +3723,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3198
3723
  const spaceId = this.resolvePermissionSpace(entry.space, session);
3199
3724
  const service = this.shortServiceName(entry.service);
3200
3725
  return entry.actions.map((action) => ({
3201
- spaceId,
3726
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
3202
3727
  service,
3203
3728
  path: entry.path,
3204
3729
  action
@@ -3255,24 +3780,40 @@ var _TinyCloudNode = class _TinyCloudNode {
3255
3780
  expiresAt: delegation.expiry
3256
3781
  };
3257
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
+ }
3258
3798
  delegatedResourcesForEntries(entries, spaceId) {
3259
3799
  return entries.map((entry) => ({
3260
3800
  service: this.shortServiceName(entry.service),
3261
- space: spaceId,
3801
+ space: this.isEncryptionPermissionEntry(entry) ? "encryption" : spaceId,
3262
3802
  path: entry.path,
3263
3803
  actions: [...entry.actions]
3264
3804
  }));
3265
3805
  }
3266
3806
  operationsFromDelegation(delegation) {
3267
3807
  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),
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,
3272
3813
  path: resource.path,
3273
3814
  action
3274
- }))
3275
- );
3815
+ }));
3816
+ });
3276
3817
  }
3277
3818
  flatDelegationResources(delegation) {
3278
3819
  const byService = /* @__PURE__ */ new Map();
@@ -3321,7 +3862,13 @@ var _TinyCloudNode = class _TinyCloudNode {
3321
3862
  );
3322
3863
  }
3323
3864
  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);
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);
3325
3872
  }
3326
3873
  actionContains(grantedAction, requestedAction) {
3327
3874
  if (grantedAction === requestedAction) {
@@ -3336,6 +3883,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3336
3883
  invocationServiceName(service) {
3337
3884
  return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3338
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
+ }
3339
3917
  pathContains(grantedPath, requestedPath) {
3340
3918
  if (grantedPath === "" || grantedPath === "/") {
3341
3919
  return true;
@@ -3574,6 +4152,17 @@ var _TinyCloudNode = class _TinyCloudNode {
3574
4152
  // Not used in session-only mode
3575
4153
  };
3576
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
+ );
3577
4166
  return new DelegatedAccess(session2, delegation, targetHost, this.wasmBindings.invoke);
3578
4167
  }
3579
4168
  const mySession = this.auth?.tinyCloudSession;
@@ -3585,6 +4174,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3585
4174
  const kvActions = delegation.actions.filter((a) => a.startsWith("tinycloud.kv/"));
3586
4175
  const sqlActions = delegation.actions.filter((a) => a.startsWith("tinycloud.sql/"));
3587
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 = {};
3588
4181
  if (kvActions.length > 0) {
3589
4182
  abilities.kv = { [delegation.path]: kvActions };
3590
4183
  }
@@ -3594,6 +4187,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3594
4187
  if (duckdbActions.length > 0) {
3595
4188
  abilities.duckdb = { [delegation.path]: duckdbActions };
3596
4189
  }
4190
+ if (encryptionActions.length > 0 && delegation.path.startsWith("urn:tinycloud:encryption:")) {
4191
+ rawAbilities[delegation.path] = encryptionActions;
4192
+ }
3597
4193
  const now = /* @__PURE__ */ new Date();
3598
4194
  const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
3599
4195
  const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
@@ -3606,7 +4202,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3606
4202
  expirationTime: expirationTime.toISOString(),
3607
4203
  spaceId: delegation.spaceId,
3608
4204
  jwk,
3609
- parents: [delegation.cid]
4205
+ parents: [delegation.cid],
4206
+ ...Object.keys(rawAbilities).length > 0 ? { rawAbilities } : {}
3610
4207
  });
3611
4208
  const signature = await this.signer.signMessage(prepared.siwe);
3612
4209
  const invokerSession = this.wasmBindings.completeSessionSetup({
@@ -3633,6 +4230,17 @@ var _TinyCloudNode = class _TinyCloudNode {
3633
4230
  signature
3634
4231
  };
3635
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
+ );
3636
4244
  return new DelegatedAccess(session, delegation, targetHost, this.wasmBindings.invoke);
3637
4245
  }
3638
4246
  /**
@@ -3753,7 +4361,7 @@ import {
3753
4361
  loadManifest,
3754
4362
  isCapabilitySubset as isCapabilitySubset2,
3755
4363
  expandActionShortNames,
3756
- expandPermissionEntries,
4364
+ expandPermissionEntries as expandPermissionEntries2,
3757
4365
  expandPermissionEntry,
3758
4366
  parseExpiry as parseExpiry2,
3759
4367
  resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
@@ -3776,10 +4384,24 @@ function deserializeDelegation(data) {
3776
4384
  }
3777
4385
 
3778
4386
  // src/core.ts
3779
- 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";
3780
4392
  import { SQLService as SQLService3, SQLAction, DatabaseHandle } from "@tinycloud/sdk-core";
3781
4393
  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";
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";
3783
4405
  import {
3784
4406
  DelegationManager as DelegationManager2,
3785
4407
  SharingService as SharingService2,
@@ -3815,6 +4437,7 @@ export {
3815
4437
  CapabilityKeyRegistryErrorCodes,
3816
4438
  DEFAULT_MANIFEST_SPACE2 as DEFAULT_MANIFEST_SPACE,
3817
4439
  DEFAULT_MANIFEST_VERSION,
4440
+ DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
3818
4441
  DataVaultService2 as DataVaultService,
3819
4442
  DatabaseHandle,
3820
4443
  DelegatedAccess,
@@ -3831,6 +4454,7 @@ export {
3831
4454
  PermissionNotInManifestError2 as PermissionNotInManifestError,
3832
4455
  PrefixedKVService,
3833
4456
  ProtocolMismatchError,
4457
+ SECRET_NAME_RE,
3834
4458
  SQLAction,
3835
4459
  SQLService3 as SQLService,
3836
4460
  SecretsService2 as SecretsService,
@@ -3850,6 +4474,7 @@ export {
3850
4474
  VersionCheckError,
3851
4475
  WasmKeyProvider,
3852
4476
  buildSpaceUri,
4477
+ canonicalizeSecretScope,
3853
4478
  checkNodeInfo2 as checkNodeInfo,
3854
4479
  composeManifestRequest2 as composeManifestRequest,
3855
4480
  createCapabilityKeyRegistry,
@@ -3861,7 +4486,7 @@ export {
3861
4486
  defaultSpaceCreationHandler,
3862
4487
  deserializeDelegation,
3863
4488
  expandActionShortNames,
3864
- expandPermissionEntries,
4489
+ expandPermissionEntries2 as expandPermissionEntries,
3865
4490
  expandPermissionEntry,
3866
4491
  isCapabilitySubset2 as isCapabilitySubset,
3867
4492
  loadManifest,
@@ -3869,6 +4494,8 @@ export {
3869
4494
  parseExpiry2 as parseExpiry,
3870
4495
  parseSpaceUri,
3871
4496
  resolveManifest2 as resolveManifest,
4497
+ resolveSecretListPrefix2 as resolveSecretListPrefix,
4498
+ resolveSecretPath2 as resolveSecretPath,
3872
4499
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
3873
4500
  serializeDelegation,
3874
4501
  validateManifest