@tinycloud/node-sdk 2.2.0-beta.7 → 2.2.0-beta.9

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.cjs CHANGED
@@ -56,6 +56,7 @@ __export(core_exports, {
56
56
  TinyCloud: () => import_sdk_core7.TinyCloud,
57
57
  TinyCloudNode: () => TinyCloudNode,
58
58
  UnsupportedFeatureError: () => import_sdk_core17.UnsupportedFeatureError,
59
+ VAULT_PERMISSION_SERVICE: () => import_sdk_core9.VAULT_PERMISSION_SERVICE,
59
60
  VaultHeaders: () => import_sdk_core13.VaultHeaders,
60
61
  VaultPublicSpaceKVActions: () => import_sdk_core13.VaultPublicSpaceKVActions,
61
62
  VersionCheckError: () => import_sdk_core17.VersionCheckError,
@@ -72,6 +73,8 @@ __export(core_exports, {
72
73
  defaultSpaceCreationHandler: () => import_sdk_core8.defaultSpaceCreationHandler,
73
74
  deserializeDelegation: () => deserializeDelegation,
74
75
  expandActionShortNames: () => import_sdk_core9.expandActionShortNames,
76
+ expandPermissionEntries: () => import_sdk_core9.expandPermissionEntries,
77
+ expandPermissionEntry: () => import_sdk_core9.expandPermissionEntry,
75
78
  isCapabilitySubset: () => import_sdk_core9.isCapabilitySubset,
76
79
  loadManifest: () => import_sdk_core9.loadManifest,
77
80
  makePublicSpaceId: () => import_sdk_core16.makePublicSpaceId,
@@ -1272,49 +1275,29 @@ function secretsError(code, message, cause) {
1272
1275
  }
1273
1276
  };
1274
1277
  }
1275
- function actionUrn(action) {
1276
- return `tinycloud.kv/${action}`;
1278
+ function displayActionUrn(action) {
1279
+ return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
1277
1280
  }
1278
- function secretResourcePath(base, name) {
1279
- return `${base}/${SECRET_PREFIX}${name}`;
1281
+ function kvActionUrn(action) {
1282
+ return `tinycloud.kv/${action}`;
1280
1283
  }
1281
1284
  function secretPermissionEntries(name, action) {
1282
1285
  return [
1283
1286
  {
1284
- service: "tinycloud.kv",
1287
+ service: "tinycloud.vault",
1285
1288
  space: SECRETS_SPACE,
1286
- path: secretResourcePath("keys", name),
1287
- actions: [action],
1288
- skipPrefix: true
1289
- },
1290
- {
1291
- service: "tinycloud.kv",
1292
- space: SECRETS_SPACE,
1293
- path: secretResourcePath("vault", name),
1294
- actions: [action],
1289
+ path: `${SECRET_PREFIX}${name}`,
1290
+ actions: [action === "put" ? "write" : "delete"],
1295
1291
  skipPrefix: true
1296
1292
  }
1297
1293
  ];
1298
1294
  }
1295
+ function secretResourcePath(base, name) {
1296
+ return `${base}/${SECRET_PREFIX}${name}`;
1297
+ }
1299
1298
  function isSecretsSpace(space) {
1300
1299
  return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1301
1300
  }
1302
- function composeEscalatedManifest(manifest, additional) {
1303
- if (Array.isArray(manifest)) {
1304
- const [primary, ...rest] = manifest;
1305
- return [
1306
- {
1307
- ...primary,
1308
- permissions: [...primary.permissions ?? [], ...additional]
1309
- },
1310
- ...rest
1311
- ];
1312
- }
1313
- return {
1314
- ...manifest,
1315
- permissions: [...manifest.permissions ?? [], ...additional]
1316
- };
1317
- }
1318
1301
  var NodeSecretsService = class {
1319
1302
  constructor(config) {
1320
1303
  this.config = config;
@@ -1327,10 +1310,11 @@ var NodeSecretsService = class {
1327
1310
  return this.service.isUnlocked;
1328
1311
  }
1329
1312
  async unlock(signer) {
1330
- if (signer !== void 0) {
1331
- this.unlockSigner = signer;
1313
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
1314
+ if (effectiveSigner !== void 0) {
1315
+ this.unlockSigner = effectiveSigner;
1332
1316
  }
1333
- const result = await this.service.unlock(signer);
1317
+ const result = await this.service.unlock(effectiveSigner);
1334
1318
  if (result.ok) {
1335
1319
  this.shouldRestoreUnlock = true;
1336
1320
  }
@@ -1372,29 +1356,16 @@ var NodeSecretsService = class {
1372
1356
  if (!this.config.canEscalate()) {
1373
1357
  return secretsError(
1374
1358
  import_sdk_core5.ErrorCodes.PERMISSION_DENIED,
1375
- `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1376
- );
1377
- }
1378
- const manifest = this.config.getManifest();
1379
- if (manifest === void 0) {
1380
- return secretsError(
1381
- import_sdk_core5.ErrorCodes.PERMISSION_DENIED,
1382
- `Cannot autosign ${actionUrn(action)} for ${name}; set a manifest before mutating secrets.`
1359
+ `Cannot autosign ${displayActionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1383
1360
  );
1384
1361
  }
1385
1362
  try {
1386
- this.config.setManifest(
1387
- composeEscalatedManifest(
1388
- manifest,
1389
- secretPermissionEntries(name, action)
1390
- )
1391
- );
1392
- await this.config.signIn();
1363
+ await this.config.grantPermissions(secretPermissionEntries(name, action));
1393
1364
  return this.restoreUnlockAfterEscalation();
1394
1365
  } catch (error) {
1395
1366
  return secretsError(
1396
1367
  import_sdk_core5.ErrorCodes.PERMISSION_DENIED,
1397
- error instanceof Error ? error.message : `Autosign escalation for ${actionUrn(action)} on ${name} failed.`,
1368
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
1398
1369
  error instanceof Error ? error : void 0
1399
1370
  );
1400
1371
  }
@@ -1411,7 +1382,7 @@ var NodeSecretsService = class {
1411
1382
  return false;
1412
1383
  }
1413
1384
  const manifests = Array.isArray(manifest) ? manifest : [manifest];
1414
- const requiredAction = actionUrn(action);
1385
+ const requiredAction = kvActionUrn(action);
1415
1386
  return manifests.some((entry) => {
1416
1387
  const resolved = (0, import_sdk_core5.resolveManifest)(entry);
1417
1388
  return ["keys", "vault"].every(
@@ -1454,6 +1425,30 @@ var _TinyCloudNode = class _TinyCloudNode {
1454
1425
  this.auth = null;
1455
1426
  this.tc = null;
1456
1427
  this._chainId = 1;
1428
+ this.runtimePermissionGrants = [];
1429
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
1430
+ return this.wasmBindings.invoke(
1431
+ this.selectInvocationSession(session, service, path, action),
1432
+ service,
1433
+ path,
1434
+ action,
1435
+ facts
1436
+ );
1437
+ };
1438
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
1439
+ if (!this.wasmBindings.invokeAny) {
1440
+ throw new Error("WASM binding does not support invokeAny");
1441
+ }
1442
+ const grant = this.findGrantForOperations(
1443
+ entries.map((entry) => ({
1444
+ spaceId: entry.spaceId,
1445
+ service: this.invocationServiceName(entry.service),
1446
+ path: entry.path,
1447
+ action: entry.action
1448
+ }))
1449
+ );
1450
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1451
+ };
1457
1452
  this.explicitHost = config.host;
1458
1453
  this.config = {
1459
1454
  ...config,
@@ -1489,7 +1484,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1489
1484
  this._sharingService = new import_sdk_core6.SharingService({
1490
1485
  hosts: [this.config.host],
1491
1486
  // session: undefined - not needed for receive()
1492
- invoke: this.wasmBindings.invoke,
1487
+ invoke: this.invokeWithRuntimePermissions,
1493
1488
  fetch: globalThis.fetch.bind(globalThis),
1494
1489
  keyProvider: this._keyProvider,
1495
1490
  registry: this._capabilityRegistry,
@@ -1557,7 +1552,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1557
1552
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
1558
1553
  });
1559
1554
  this.tc = new import_sdk_core6.TinyCloud(this.auth, {
1560
- invokeAny: this.wasmBindings.invokeAny
1555
+ invokeAny: this.invokeAnyWithRuntimePermissions
1561
1556
  });
1562
1557
  }
1563
1558
  syncResolvedHostFromAuth() {
@@ -1682,6 +1677,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1682
1677
  this._secrets = void 0;
1683
1678
  this._spaceService = void 0;
1684
1679
  this._serviceContext = void 0;
1680
+ this.runtimePermissionGrants = [];
1685
1681
  await this.tc.signIn(options);
1686
1682
  this.syncResolvedHostFromAuth();
1687
1683
  this.initializeServices();
@@ -1771,6 +1767,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1771
1767
  this._secrets = void 0;
1772
1768
  this._spaceService = void 0;
1773
1769
  this._serviceContext = void 0;
1770
+ this.runtimePermissionGrants = [];
1774
1771
  if (sessionData.address) {
1775
1772
  this._address = sessionData.address;
1776
1773
  }
@@ -1778,8 +1775,8 @@ var _TinyCloudNode = class _TinyCloudNode {
1778
1775
  this._chainId = sessionData.chainId;
1779
1776
  }
1780
1777
  this._serviceContext = new import_sdk_core6.ServiceContext({
1781
- invoke: this.wasmBindings.invoke,
1782
- invokeAny: this.wasmBindings.invokeAny,
1778
+ invoke: this.invokeWithRuntimePermissions,
1779
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1783
1780
  fetch: globalThis.fetch.bind(globalThis),
1784
1781
  hosts: [this.config.host]
1785
1782
  });
@@ -1865,7 +1862,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1865
1862
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1866
1863
  });
1867
1864
  this.tc = new import_sdk_core6.TinyCloud(this.auth, {
1868
- invokeAny: this.wasmBindings.invokeAny
1865
+ invokeAny: this.invokeAnyWithRuntimePermissions
1869
1866
  });
1870
1867
  this.config.prefix = prefix;
1871
1868
  }
@@ -1909,7 +1906,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1909
1906
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1910
1907
  });
1911
1908
  this.tc = new import_sdk_core6.TinyCloud(this.auth, {
1912
- invokeAny: this.wasmBindings.invokeAny
1909
+ invokeAny: this.invokeAnyWithRuntimePermissions
1913
1910
  });
1914
1911
  this.config.prefix = prefix;
1915
1912
  }
@@ -1922,10 +1919,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1922
1919
  if (!session) {
1923
1920
  return;
1924
1921
  }
1925
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
1922
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
1926
1923
  this._serviceContext = new import_sdk_core6.ServiceContext({
1927
- invoke: this.wasmBindings.invoke,
1928
- invokeAny: this.wasmBindings.invokeAny,
1924
+ invoke: this.invokeWithRuntimePermissions,
1925
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1929
1926
  fetch: globalThis.fetch.bind(globalThis),
1930
1927
  hosts: [this.config.host]
1931
1928
  });
@@ -2095,7 +2092,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2095
2092
  this._delegationManager = new import_sdk_core6.DelegationManager({
2096
2093
  hosts: [this.config.host],
2097
2094
  session: serviceSession,
2098
- invoke: this.wasmBindings.invoke,
2095
+ invoke: this.invokeWithRuntimePermissions,
2099
2096
  fetch: globalThis.fetch.bind(globalThis)
2100
2097
  });
2101
2098
  this._spaceService = new import_sdk_core6.SpaceService({
@@ -2362,9 +2359,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2362
2359
  this._secrets = new NodeSecretsService({
2363
2360
  getService: () => this.getBaseSecrets(),
2364
2361
  getManifest: () => this.manifest,
2365
- setManifest: (manifest) => this.setManifest(manifest),
2366
- signIn: () => this.signIn(),
2367
- canEscalate: () => this.signer !== void 0 && this.tc !== void 0
2362
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2363
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2364
+ getUnlockSigner: () => this.signer ?? void 0
2368
2365
  });
2369
2366
  }
2370
2367
  return this._secrets;
@@ -2448,6 +2445,171 @@ var _TinyCloudNode = class _TinyCloudNode {
2448
2445
  }
2449
2446
  };
2450
2447
  }
2448
+ /**
2449
+ * Check whether the current session or an approved runtime delegation covers
2450
+ * every requested permission.
2451
+ */
2452
+ hasRuntimePermissions(permissions) {
2453
+ const session = this.auth?.tinyCloudSession;
2454
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2455
+ return false;
2456
+ }
2457
+ const expanded = this.expandPermissionEntries(permissions);
2458
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2459
+ return true;
2460
+ }
2461
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
2462
+ }
2463
+ /**
2464
+ * Return installed runtime permission delegations. When `permissions` is
2465
+ * provided, only delegations currently covering those permissions are
2466
+ * returned. Base-session manifest permissions are not represented here.
2467
+ */
2468
+ getRuntimePermissionDelegations(permissions) {
2469
+ this.pruneExpiredRuntimePermissionGrants();
2470
+ if (permissions === void 0) {
2471
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
2472
+ }
2473
+ const session = this.auth?.tinyCloudSession;
2474
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2475
+ return [];
2476
+ }
2477
+ const expanded = this.expandPermissionEntries(permissions);
2478
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
2479
+ (grant) => grant.delegation
2480
+ );
2481
+ }
2482
+ /**
2483
+ * Install a portable runtime permission delegation into this SDK instance so
2484
+ * matching service calls and downstream `delegateTo()` calls can use it.
2485
+ */
2486
+ async useRuntimeDelegation(delegation) {
2487
+ const session = this.auth?.tinyCloudSession;
2488
+ if (!session) {
2489
+ throw new import_sdk_core6.SessionExpiredError(/* @__PURE__ */ new Date(0));
2490
+ }
2491
+ if (delegation.expiry.getTime() <= Date.now()) {
2492
+ throw new import_sdk_core6.SessionExpiredError(delegation.expiry);
2493
+ }
2494
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
2495
+ if (!expectedDids.has(delegation.delegateDID)) {
2496
+ throw new Error(
2497
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
2498
+ );
2499
+ }
2500
+ const targetHost = delegation.host ?? this.config.host;
2501
+ const activateResult = await (0, import_sdk_core6.activateSessionWithHost)(
2502
+ targetHost,
2503
+ delegation.delegationHeader
2504
+ );
2505
+ if (!activateResult.success) {
2506
+ throw new Error(
2507
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2508
+ );
2509
+ }
2510
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
2511
+ (grant) => grant.delegation.cid !== delegation.cid
2512
+ );
2513
+ this.runtimePermissionGrants.push(
2514
+ this.runtimeGrantFromDelegation(delegation, session)
2515
+ );
2516
+ }
2517
+ /**
2518
+ * Store additional permissions as narrow delegations to the current session
2519
+ * key. Future service invocations automatically use a stored delegation when
2520
+ * its `(space, service, path, action)` covers the request.
2521
+ */
2522
+ async grantRuntimePermissions(permissions, options) {
2523
+ if (!Array.isArray(permissions) || permissions.length === 0) {
2524
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2525
+ }
2526
+ const session = this.auth?.tinyCloudSession;
2527
+ if (!session) {
2528
+ throw new import_sdk_core6.SessionExpiredError(/* @__PURE__ */ new Date(0));
2529
+ }
2530
+ const sessionExpiry = extractSiweExpiration(session.siwe);
2531
+ if (sessionExpiry !== void 0) {
2532
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2533
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
2534
+ throw new import_sdk_core6.SessionExpiredError(sessionExpiry);
2535
+ }
2536
+ }
2537
+ const expanded = this.expandPermissionEntries(permissions);
2538
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2539
+ return [];
2540
+ }
2541
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
2542
+ if (existingGrants.length > 0) {
2543
+ return existingGrants.map((grant) => grant.delegation);
2544
+ }
2545
+ if (!this.signer) {
2546
+ throw new Error(
2547
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2548
+ );
2549
+ }
2550
+ const bySpace = /* @__PURE__ */ new Map();
2551
+ for (const entry of expanded) {
2552
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
2553
+ const current = bySpace.get(spaceId) ?? [];
2554
+ current.push(entry);
2555
+ bySpace.set(spaceId, current);
2556
+ }
2557
+ const now = /* @__PURE__ */ new Date();
2558
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2559
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
2560
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
2561
+ expiresAt = sessionExpiry;
2562
+ }
2563
+ const delegations = [];
2564
+ for (const [spaceId, entries] of bySpace) {
2565
+ const abilities = this.permissionsToAbilities(entries);
2566
+ const prepared = this.wasmBindings.prepareSession({
2567
+ abilities,
2568
+ address: this.wasmBindings.ensureEip55(session.address),
2569
+ chainId: session.chainId,
2570
+ domain: this.siweDomain,
2571
+ issuedAt: now.toISOString(),
2572
+ expirationTime: expiresAt.toISOString(),
2573
+ spaceId,
2574
+ jwk: session.jwk
2575
+ });
2576
+ const signature = await this.signer.signMessage(prepared.siwe);
2577
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
2578
+ ...prepared,
2579
+ signature
2580
+ });
2581
+ const activateResult = await (0, import_sdk_core6.activateSessionWithHost)(
2582
+ this.config.host,
2583
+ delegatedSession.delegationHeader
2584
+ );
2585
+ if (!activateResult.success) {
2586
+ throw new Error(
2587
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2588
+ );
2589
+ }
2590
+ const delegation = this.runtimeDelegationFromSession(
2591
+ delegatedSession,
2592
+ entries,
2593
+ spaceId,
2594
+ session,
2595
+ expiresAt
2596
+ );
2597
+ this.runtimePermissionGrants.push({
2598
+ session: {
2599
+ delegationHeader: delegatedSession.delegationHeader,
2600
+ delegationCid: delegatedSession.delegationCid,
2601
+ spaceId,
2602
+ verificationMethod: session.verificationMethod,
2603
+ jwk: session.jwk
2604
+ },
2605
+ delegation,
2606
+ operations: this.permissionOperations(entries, spaceId),
2607
+ expiresAt
2608
+ });
2609
+ delegations.push(delegation);
2610
+ }
2611
+ return delegations;
2612
+ }
2451
2613
  /**
2452
2614
  * Get the DelegationManager for delegation CRUD operations.
2453
2615
  *
@@ -2636,7 +2798,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2636
2798
  if (this._serviceContext) {
2637
2799
  const publicKV = new import_sdk_core6.KVService({ prefix: "" });
2638
2800
  const publicContext = new import_sdk_core6.ServiceContext({
2639
- invoke: this.wasmBindings.invoke,
2801
+ invoke: this.invokeWithRuntimePermissions,
2640
2802
  fetch: this._serviceContext.fetch,
2641
2803
  hosts: this._serviceContext.hosts
2642
2804
  });
@@ -2724,8 +2886,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2724
2886
  * Issue a delegation using the capability-chain flow.
2725
2887
  *
2726
2888
  * When every requested permission is a subset of the current
2727
- * session's recap, the delegation is signed by the session key via
2728
- * WASM no wallet prompt. When at least one is NOT derivable, a
2889
+ * session's recap, or of one installed runtime permission delegation,
2890
+ * the delegation is signed by the session key via WASM no wallet
2891
+ * prompt. When at least one is NOT derivable, a
2729
2892
  * {@link PermissionNotInManifestError} is raised (carrying the
2730
2893
  * missing entries) so the caller can trigger an escalation flow
2731
2894
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -2775,10 +2938,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2775
2938
  "delegateTo requires a non-empty permissions array"
2776
2939
  );
2777
2940
  }
2778
- const expandedEntries = permissions.map((entry) => ({
2779
- ...entry,
2780
- actions: (0, import_sdk_core6.expandActionShortNames)(entry.service, entry.actions)
2781
- }));
2941
+ const expandedEntries = this.expandPermissionEntries(permissions);
2782
2942
  const now = /* @__PURE__ */ new Date();
2783
2943
  const expiryMs = resolveExpiryMs(options?.expiry);
2784
2944
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -2805,6 +2965,23 @@ var _TinyCloudNode = class _TinyCloudNode {
2805
2965
  );
2806
2966
  const { subset, missing } = (0, import_sdk_core6.isCapabilitySubset)(expandedEntries, granted);
2807
2967
  if (!subset) {
2968
+ const runtimeGrant = this.findGrantForOperations(
2969
+ this.permissionEntriesToOperations(expandedEntries, session)
2970
+ );
2971
+ if (runtimeGrant) {
2972
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2973
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
2974
+ throw new import_sdk_core6.SessionExpiredError(runtimeGrant.expiresAt);
2975
+ }
2976
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
2977
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
2978
+ did,
2979
+ expandedEntries,
2980
+ runtimeExpiration,
2981
+ runtimeGrant
2982
+ );
2983
+ return { delegation: delegation2, prompted: false };
2984
+ }
2808
2985
  throw new import_sdk_core6.PermissionNotInManifestError(missing, granted);
2809
2986
  }
2810
2987
  const delegation = await this.createDelegationViaWasmPath(
@@ -2949,6 +3126,41 @@ var _TinyCloudNode = class _TinyCloudNode {
2949
3126
  host: this.config.host
2950
3127
  };
2951
3128
  }
3129
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
3130
+ const result = this.createDelegationWrapper({
3131
+ session: grant.session,
3132
+ delegateDID: did,
3133
+ spaceId: grant.session.spaceId,
3134
+ abilities: this.permissionsToAbilities(entries),
3135
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
3136
+ });
3137
+ const primary = result.resources[0];
3138
+ const delegationHeader = { Authorization: result.delegation };
3139
+ const targetHost = grant.delegation.host ?? this.config.host;
3140
+ const activateResult = await (0, import_sdk_core6.activateSessionWithHost)(
3141
+ targetHost,
3142
+ delegationHeader
3143
+ );
3144
+ if (!activateResult.success) {
3145
+ throw new Error(
3146
+ `Failed to activate delegation with host: ${activateResult.error}`
3147
+ );
3148
+ }
3149
+ return {
3150
+ cid: result.cid,
3151
+ delegationHeader,
3152
+ spaceId: grant.session.spaceId,
3153
+ path: primary.path,
3154
+ actions: primary.actions,
3155
+ resources: result.resources,
3156
+ disableSubDelegation: false,
3157
+ expiry: result.expiry,
3158
+ delegateDID: did,
3159
+ ownerAddress: grant.delegation.ownerAddress,
3160
+ chainId: grant.delegation.chainId,
3161
+ host: targetHost
3162
+ };
3163
+ }
2952
3164
  resolvePermissionSpace(space, session) {
2953
3165
  if (space === void 0) {
2954
3166
  return this.wasmBindings.makeSpaceId(
@@ -2965,6 +3177,220 @@ var _TinyCloudNode = class _TinyCloudNode {
2965
3177
  }
2966
3178
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
2967
3179
  }
3180
+ expandPermissionEntries(permissions) {
3181
+ return (0, import_sdk_core6.expandPermissionEntries)(permissions);
3182
+ }
3183
+ shortServiceName(service) {
3184
+ const short = import_sdk_core6.SERVICE_LONG_TO_SHORT[service];
3185
+ if (short === void 0) {
3186
+ throw new Error(
3187
+ `unknown service '${service}' \u2014 no short-form mapping`
3188
+ );
3189
+ }
3190
+ return short;
3191
+ }
3192
+ permissionsToAbilities(entries) {
3193
+ const abilities = {};
3194
+ for (const entry of entries) {
3195
+ const service = this.shortServiceName(entry.service);
3196
+ abilities[service] ?? (abilities[service] = {});
3197
+ const existing = abilities[service][entry.path] ?? [];
3198
+ const seen = new Set(existing);
3199
+ for (const action of entry.actions) {
3200
+ if (!seen.has(action)) {
3201
+ existing.push(action);
3202
+ seen.add(action);
3203
+ }
3204
+ }
3205
+ abilities[service][entry.path] = existing;
3206
+ }
3207
+ return abilities;
3208
+ }
3209
+ permissionOperations(entries, spaceId) {
3210
+ return entries.flatMap((entry) => {
3211
+ const service = this.shortServiceName(entry.service);
3212
+ return entry.actions.map((action) => ({
3213
+ spaceId,
3214
+ service,
3215
+ path: entry.path,
3216
+ action
3217
+ }));
3218
+ });
3219
+ }
3220
+ sessionCoversPermissionEntries(session, entries) {
3221
+ try {
3222
+ const granted = (0, import_sdk_core6.parseRecapCapabilities)(
3223
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
3224
+ session.siwe
3225
+ );
3226
+ return (0, import_sdk_core6.isCapabilitySubset)(entries, granted).subset;
3227
+ } catch {
3228
+ return false;
3229
+ }
3230
+ }
3231
+ permissionEntriesToOperations(entries, session) {
3232
+ return entries.flatMap((entry) => {
3233
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
3234
+ const service = this.shortServiceName(entry.service);
3235
+ return entry.actions.map((action) => ({
3236
+ spaceId,
3237
+ service,
3238
+ path: entry.path,
3239
+ action
3240
+ }));
3241
+ });
3242
+ }
3243
+ findRuntimeGrantsForPermissionEntries(entries, session) {
3244
+ const grants = [];
3245
+ const operations = this.permissionEntriesToOperations(entries, session);
3246
+ if (operations.length === 0) {
3247
+ return grants;
3248
+ }
3249
+ for (const operation of operations) {
3250
+ const grant = this.findGrantForOperation(operation);
3251
+ if (!grant) {
3252
+ return [];
3253
+ }
3254
+ if (!grants.includes(grant)) {
3255
+ grants.push(grant);
3256
+ }
3257
+ }
3258
+ return grants;
3259
+ }
3260
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
3261
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
3262
+ const primary = resources[0];
3263
+ return {
3264
+ cid: delegatedSession.delegationCid,
3265
+ delegationHeader: delegatedSession.delegationHeader,
3266
+ spaceId,
3267
+ path: primary.path,
3268
+ actions: primary.actions,
3269
+ resources,
3270
+ disableSubDelegation: false,
3271
+ expiry: expiresAt,
3272
+ delegateDID: session.verificationMethod,
3273
+ ownerAddress: session.address,
3274
+ chainId: session.chainId,
3275
+ host: this.config.host
3276
+ };
3277
+ }
3278
+ runtimeGrantFromDelegation(delegation, session) {
3279
+ const operations = this.operationsFromDelegation(delegation);
3280
+ return {
3281
+ session: {
3282
+ delegationHeader: delegation.delegationHeader,
3283
+ delegationCid: delegation.cid,
3284
+ spaceId: delegation.spaceId,
3285
+ verificationMethod: session.verificationMethod,
3286
+ jwk: session.jwk
3287
+ },
3288
+ delegation,
3289
+ operations,
3290
+ expiresAt: delegation.expiry
3291
+ };
3292
+ }
3293
+ delegatedResourcesForEntries(entries, spaceId) {
3294
+ return entries.map((entry) => ({
3295
+ service: this.shortServiceName(entry.service),
3296
+ space: spaceId,
3297
+ path: entry.path,
3298
+ actions: [...entry.actions]
3299
+ }));
3300
+ }
3301
+ operationsFromDelegation(delegation) {
3302
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3303
+ return resources.flatMap(
3304
+ (resource) => resource.actions.map((action) => ({
3305
+ spaceId: resource.space,
3306
+ service: this.invocationServiceName(resource.service),
3307
+ path: resource.path,
3308
+ action
3309
+ }))
3310
+ );
3311
+ }
3312
+ flatDelegationResources(delegation) {
3313
+ const byService = /* @__PURE__ */ new Map();
3314
+ for (const action of delegation.actions) {
3315
+ const service = this.shortServiceName(action.split("/")[0]);
3316
+ const actions = byService.get(service) ?? [];
3317
+ actions.push(action);
3318
+ byService.set(service, actions);
3319
+ }
3320
+ return [...byService.entries()].map(([service, actions]) => ({
3321
+ service,
3322
+ space: delegation.spaceId,
3323
+ path: delegation.path,
3324
+ actions
3325
+ }));
3326
+ }
3327
+ selectInvocationSession(fallback, service, path, action) {
3328
+ const grant = this.findGrantForOperation({
3329
+ spaceId: fallback.spaceId,
3330
+ service: this.invocationServiceName(service),
3331
+ path,
3332
+ action
3333
+ });
3334
+ return grant?.session ?? fallback;
3335
+ }
3336
+ findGrantForOperations(operations) {
3337
+ if (operations.length === 0) {
3338
+ return void 0;
3339
+ }
3340
+ this.pruneExpiredRuntimePermissionGrants();
3341
+ return this.runtimePermissionGrants.find((grant) => {
3342
+ return operations.every(
3343
+ (operation) => grant.operations.some(
3344
+ (granted) => this.operationCovers(granted, operation)
3345
+ )
3346
+ );
3347
+ });
3348
+ }
3349
+ findGrantForOperation(operation) {
3350
+ return this.findGrantForOperations([operation]);
3351
+ }
3352
+ pruneExpiredRuntimePermissionGrants() {
3353
+ const now = Date.now();
3354
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3355
+ (grant) => grant.expiresAt.getTime() > now
3356
+ );
3357
+ }
3358
+ operationCovers(granted, requested) {
3359
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3360
+ }
3361
+ actionContains(grantedAction, requestedAction) {
3362
+ if (grantedAction === requestedAction) {
3363
+ return true;
3364
+ }
3365
+ if (grantedAction.endsWith("/*")) {
3366
+ const prefix = grantedAction.slice(0, -2);
3367
+ return requestedAction.startsWith(`${prefix}/`);
3368
+ }
3369
+ return false;
3370
+ }
3371
+ invocationServiceName(service) {
3372
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3373
+ }
3374
+ pathContains(grantedPath, requestedPath) {
3375
+ if (grantedPath === "" || grantedPath === "/") {
3376
+ return true;
3377
+ }
3378
+ if (grantedPath.endsWith("/**")) {
3379
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
3380
+ }
3381
+ if (grantedPath.endsWith("/*")) {
3382
+ const prefix = grantedPath.slice(0, -2);
3383
+ if (!requestedPath.startsWith(prefix)) {
3384
+ return false;
3385
+ }
3386
+ const remainder = requestedPath.slice(prefix.length);
3387
+ return !remainder.includes("/") || remainder === "/";
3388
+ }
3389
+ if (grantedPath.endsWith("/")) {
3390
+ return requestedPath.startsWith(grantedPath);
3391
+ }
3392
+ return grantedPath === requestedPath;
3393
+ }
2968
3394
  /**
2969
3395
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
2970
3396
  * {@link PermissionEntry}. Shares the implementation with the public
@@ -3413,6 +3839,7 @@ var import_sdk_core18 = require("@tinycloud/sdk-core");
3413
3839
  TinyCloud,
3414
3840
  TinyCloudNode,
3415
3841
  UnsupportedFeatureError,
3842
+ VAULT_PERMISSION_SERVICE,
3416
3843
  VaultHeaders,
3417
3844
  VaultPublicSpaceKVActions,
3418
3845
  VersionCheckError,
@@ -3429,6 +3856,8 @@ var import_sdk_core18 = require("@tinycloud/sdk-core");
3429
3856
  defaultSpaceCreationHandler,
3430
3857
  deserializeDelegation,
3431
3858
  expandActionShortNames,
3859
+ expandPermissionEntries,
3860
+ expandPermissionEntry,
3432
3861
  isCapabilitySubset,
3433
3862
  loadManifest,
3434
3863
  makePublicSpaceId,