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

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.d.cts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { ACCOUNT_REGISTRY_PATH, ACCOUNT_REGISTRY_SPACE, AutoApproveSpaceCreationHandler, AutoRejectStrategy, AutoSignStrategy, BatchOptions, BatchResponse, CallbackStrategy, CapabilityEntry, CapabilityKeyRegistry, CapabilityKeyRegistryErrorCode, CapabilityKeyRegistryErrorCodes, ClientSession, ColumnInfo, ComposeManifestOptions, ComposedManifestRequest, CreateDelegationParams, DEFAULT_MANIFEST_SPACE, DEFAULT_MANIFEST_VERSION, DataVaultConfig, DataVaultService, DatabaseHandle, Delegation, DelegationChain, DelegationChainV2, DelegationDirection, DelegationError, DelegationErrorCode, DelegationErrorCodes, DelegationFilters, DelegationManager, DelegationManagerConfig, DelegationRecord, DelegationResult, DuckDbAction, DuckDbActionType, DuckDbBatchOptions, DuckDbBatchResponse, DuckDbDatabaseHandle, DuckDbExecuteOptions, DuckDbExecuteResponse, DuckDbOptions, DuckDbQueryOptions, DuckDbQueryResponse, DuckDbService, DuckDbServiceConfig, DuckDbStatement, DuckDbValue, EncodedShareData, ExecuteOptions, ExecuteResponse, Extension, FetchFunction, GenerateShareParams, ICapabilityKeyRegistry, IDataVaultService, IDatabaseHandle, IDuckDbDatabaseHandle, IDuckDbService, IENSResolver, IKVService, INotificationHandler, IPrefixedKVService, ISQLService, ISecretsService, ISessionManager, ISessionStorage, ISharingService, ISigner, ISpace, ISpaceCreationHandler, ISpaceScopedDelegations, ISpaceScopedSharing, ISpaceService, IUserAuthorization, IWasmBindings, IngestOptions, InvokeFunction, JWK, KVResponse, KVService, KVServiceConfig, KeyInfo, KeyProvider, KeyType, Manifest, ManifestDefaults, ManifestRegistryRecord, ManifestSecretActions, ManifestValidationError, PermissionEntry, PermissionNotInManifestError, PersistedSessionData, PrefixedKVService, ProtocolMismatchError, QueryOptions, QueryResponse, ReceiveOptions, ResolvedCapabilities, ResolvedDelegate, ResourceCapability, SQLAction, SQLActionType, SQLService, SQLServiceConfig, SchemaInfo, SecretPayload, SecretsError, SecretsService, ServiceContext, ServiceContextConfig, ServiceSession, SessionExpiredError, ShareAccess, ShareLink, ShareLinkData, ShareSchema, SharingService, SharingServiceConfig, SignCallback, SignInOptions, SignRequest, SignResponse, SilentNotificationHandler, Space, SpaceAbilitiesMap, SpaceConfig, SpaceCreationContext, SpaceErrorCode, SpaceErrorCodes, SpaceInfo, SpaceOwnership, SpaceService, SpaceServiceConfig, SqlStatement, SqlValue, StoredDelegationChain, TableInfo, TinyCloud, TinyCloudConfig, TinyCloudSession, UnsupportedFeatureError, VaultCrypto, VaultEntry, VaultError, VaultGetOptions, VaultGrantOptions, VaultHeaders, VaultListOptions, VaultPublicSpaceKVActions, VaultPutOptions, VersionCheckError, ViewInfo, WasmVaultFunctions, buildSpaceUri, checkNodeInfo, composeManifestRequest, createCapabilityKeyRegistry, createSharingService, createSpaceService, createVaultCrypto, defaultSpaceCreationHandler, expandActionShortNames, isCapabilitySubset, loadManifest, makePublicSpaceId, parseExpiry, parseSpaceUri, resolveManifest, resourceCapabilitiesToSpaceAbilitiesMap, validateManifest } from '@tinycloud/sdk-core';
2
- export { D as DelegateToOptions, a as DelegateToResult, b as DelegatedAccess, F as FileSessionStorage, M as MemorySessionStorage, N as NodeEventEmitterStrategy, c as NodeUserAuthorization, d as NodeUserAuthorizationConfig, P as PortableDelegation, S as SignStrategy, T as TinyCloudNode, e as TinyCloudNodeConfig, W as WasmKeyProvider, f as WasmKeyProviderConfig, g as createWasmKeyProvider, h as defaultSignStrategy, i as deserializeDelegation, s as serializeDelegation } from './core-C3s0bgRe.cjs';
2
+ export { D as DelegateToOptions, a as DelegateToResult, b as DelegatedAccess, F as FileSessionStorage, M as MemorySessionStorage, N as NodeEventEmitterStrategy, c as NodeUserAuthorization, d as NodeUserAuthorizationConfig, P as PortableDelegation, e as RuntimePermissionGrantOptions, S as SignStrategy, T as TinyCloudNode, f as TinyCloudNodeConfig, W as WasmKeyProvider, g as WasmKeyProviderConfig, h as createWasmKeyProvider, i as defaultSignStrategy, j as deserializeDelegation, s as serializeDelegation } from './core-DcJ27GsA.cjs';
3
3
  import 'events';
4
4
  import '@tinycloud/sdk-services';
package/dist/core.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { ACCOUNT_REGISTRY_PATH, ACCOUNT_REGISTRY_SPACE, AutoApproveSpaceCreationHandler, AutoRejectStrategy, AutoSignStrategy, BatchOptions, BatchResponse, CallbackStrategy, CapabilityEntry, CapabilityKeyRegistry, CapabilityKeyRegistryErrorCode, CapabilityKeyRegistryErrorCodes, ClientSession, ColumnInfo, ComposeManifestOptions, ComposedManifestRequest, CreateDelegationParams, DEFAULT_MANIFEST_SPACE, DEFAULT_MANIFEST_VERSION, DataVaultConfig, DataVaultService, DatabaseHandle, Delegation, DelegationChain, DelegationChainV2, DelegationDirection, DelegationError, DelegationErrorCode, DelegationErrorCodes, DelegationFilters, DelegationManager, DelegationManagerConfig, DelegationRecord, DelegationResult, DuckDbAction, DuckDbActionType, DuckDbBatchOptions, DuckDbBatchResponse, DuckDbDatabaseHandle, DuckDbExecuteOptions, DuckDbExecuteResponse, DuckDbOptions, DuckDbQueryOptions, DuckDbQueryResponse, DuckDbService, DuckDbServiceConfig, DuckDbStatement, DuckDbValue, EncodedShareData, ExecuteOptions, ExecuteResponse, Extension, FetchFunction, GenerateShareParams, ICapabilityKeyRegistry, IDataVaultService, IDatabaseHandle, IDuckDbDatabaseHandle, IDuckDbService, IENSResolver, IKVService, INotificationHandler, IPrefixedKVService, ISQLService, ISecretsService, ISessionManager, ISessionStorage, ISharingService, ISigner, ISpace, ISpaceCreationHandler, ISpaceScopedDelegations, ISpaceScopedSharing, ISpaceService, IUserAuthorization, IWasmBindings, IngestOptions, InvokeFunction, JWK, KVResponse, KVService, KVServiceConfig, KeyInfo, KeyProvider, KeyType, Manifest, ManifestDefaults, ManifestRegistryRecord, ManifestSecretActions, ManifestValidationError, PermissionEntry, PermissionNotInManifestError, PersistedSessionData, PrefixedKVService, ProtocolMismatchError, QueryOptions, QueryResponse, ReceiveOptions, ResolvedCapabilities, ResolvedDelegate, ResourceCapability, SQLAction, SQLActionType, SQLService, SQLServiceConfig, SchemaInfo, SecretPayload, SecretsError, SecretsService, ServiceContext, ServiceContextConfig, ServiceSession, SessionExpiredError, ShareAccess, ShareLink, ShareLinkData, ShareSchema, SharingService, SharingServiceConfig, SignCallback, SignInOptions, SignRequest, SignResponse, SilentNotificationHandler, Space, SpaceAbilitiesMap, SpaceConfig, SpaceCreationContext, SpaceErrorCode, SpaceErrorCodes, SpaceInfo, SpaceOwnership, SpaceService, SpaceServiceConfig, SqlStatement, SqlValue, StoredDelegationChain, TableInfo, TinyCloud, TinyCloudConfig, TinyCloudSession, UnsupportedFeatureError, VaultCrypto, VaultEntry, VaultError, VaultGetOptions, VaultGrantOptions, VaultHeaders, VaultListOptions, VaultPublicSpaceKVActions, VaultPutOptions, VersionCheckError, ViewInfo, WasmVaultFunctions, buildSpaceUri, checkNodeInfo, composeManifestRequest, createCapabilityKeyRegistry, createSharingService, createSpaceService, createVaultCrypto, defaultSpaceCreationHandler, expandActionShortNames, isCapabilitySubset, loadManifest, makePublicSpaceId, parseExpiry, parseSpaceUri, resolveManifest, resourceCapabilitiesToSpaceAbilitiesMap, validateManifest } from '@tinycloud/sdk-core';
2
- export { D as DelegateToOptions, a as DelegateToResult, b as DelegatedAccess, F as FileSessionStorage, M as MemorySessionStorage, N as NodeEventEmitterStrategy, c as NodeUserAuthorization, d as NodeUserAuthorizationConfig, P as PortableDelegation, S as SignStrategy, T as TinyCloudNode, e as TinyCloudNodeConfig, W as WasmKeyProvider, f as WasmKeyProviderConfig, g as createWasmKeyProvider, h as defaultSignStrategy, i as deserializeDelegation, s as serializeDelegation } from './core-C3s0bgRe.js';
2
+ export { D as DelegateToOptions, a as DelegateToResult, b as DelegatedAccess, F as FileSessionStorage, M as MemorySessionStorage, N as NodeEventEmitterStrategy, c as NodeUserAuthorization, d as NodeUserAuthorizationConfig, P as PortableDelegation, e as RuntimePermissionGrantOptions, S as SignStrategy, T as TinyCloudNode, f as TinyCloudNodeConfig, W as WasmKeyProvider, g as WasmKeyProviderConfig, h as createWasmKeyProvider, i as defaultSignStrategy, j as deserializeDelegation, s as serializeDelegation } from './core-DcJ27GsA.js';
3
3
  import 'events';
4
4
  import '@tinycloud/sdk-services';
package/dist/core.js CHANGED
@@ -1267,22 +1267,6 @@ function secretPermissionEntries(name, action) {
1267
1267
  function isSecretsSpace(space) {
1268
1268
  return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1269
1269
  }
1270
- function composeEscalatedManifest(manifest, additional) {
1271
- if (Array.isArray(manifest)) {
1272
- const [primary, ...rest] = manifest;
1273
- return [
1274
- {
1275
- ...primary,
1276
- permissions: [...primary.permissions ?? [], ...additional]
1277
- },
1278
- ...rest
1279
- ];
1280
- }
1281
- return {
1282
- ...manifest,
1283
- permissions: [...manifest.permissions ?? [], ...additional]
1284
- };
1285
- }
1286
1270
  var NodeSecretsService = class {
1287
1271
  constructor(config) {
1288
1272
  this.config = config;
@@ -1295,10 +1279,11 @@ var NodeSecretsService = class {
1295
1279
  return this.service.isUnlocked;
1296
1280
  }
1297
1281
  async unlock(signer) {
1298
- if (signer !== void 0) {
1299
- this.unlockSigner = signer;
1282
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
1283
+ if (effectiveSigner !== void 0) {
1284
+ this.unlockSigner = effectiveSigner;
1300
1285
  }
1301
- const result = await this.service.unlock(signer);
1286
+ const result = await this.service.unlock(effectiveSigner);
1302
1287
  if (result.ok) {
1303
1288
  this.shouldRestoreUnlock = true;
1304
1289
  }
@@ -1343,21 +1328,8 @@ var NodeSecretsService = class {
1343
1328
  `Cannot autosign ${actionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1344
1329
  );
1345
1330
  }
1346
- const manifest = this.config.getManifest();
1347
- if (manifest === void 0) {
1348
- return secretsError(
1349
- ErrorCodes.PERMISSION_DENIED,
1350
- `Cannot autosign ${actionUrn(action)} for ${name}; set a manifest before mutating secrets.`
1351
- );
1352
- }
1353
1331
  try {
1354
- this.config.setManifest(
1355
- composeEscalatedManifest(
1356
- manifest,
1357
- secretPermissionEntries(name, action)
1358
- )
1359
- );
1360
- await this.config.signIn();
1332
+ await this.config.grantPermissions(secretPermissionEntries(name, action));
1361
1333
  return this.restoreUnlockAfterEscalation();
1362
1334
  } catch (error) {
1363
1335
  return secretsError(
@@ -1422,6 +1394,30 @@ var _TinyCloudNode = class _TinyCloudNode {
1422
1394
  this.auth = null;
1423
1395
  this.tc = null;
1424
1396
  this._chainId = 1;
1397
+ this.runtimePermissionGrants = [];
1398
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
1399
+ return this.wasmBindings.invoke(
1400
+ this.selectInvocationSession(session, service, path, action),
1401
+ service,
1402
+ path,
1403
+ action,
1404
+ facts
1405
+ );
1406
+ };
1407
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
1408
+ if (!this.wasmBindings.invokeAny) {
1409
+ throw new Error("WASM binding does not support invokeAny");
1410
+ }
1411
+ const grant = this.findGrantForOperations(
1412
+ entries.map((entry) => ({
1413
+ spaceId: entry.spaceId,
1414
+ service: this.invocationServiceName(entry.service),
1415
+ path: entry.path,
1416
+ action: entry.action
1417
+ }))
1418
+ );
1419
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1420
+ };
1425
1421
  this.explicitHost = config.host;
1426
1422
  this.config = {
1427
1423
  ...config,
@@ -1457,7 +1453,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1457
1453
  this._sharingService = new SharingService({
1458
1454
  hosts: [this.config.host],
1459
1455
  // session: undefined - not needed for receive()
1460
- invoke: this.wasmBindings.invoke,
1456
+ invoke: this.invokeWithRuntimePermissions,
1461
1457
  fetch: globalThis.fetch.bind(globalThis),
1462
1458
  keyProvider: this._keyProvider,
1463
1459
  registry: this._capabilityRegistry,
@@ -1525,7 +1521,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1525
1521
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
1526
1522
  });
1527
1523
  this.tc = new TinyCloud(this.auth, {
1528
- invokeAny: this.wasmBindings.invokeAny
1524
+ invokeAny: this.invokeAnyWithRuntimePermissions
1529
1525
  });
1530
1526
  }
1531
1527
  syncResolvedHostFromAuth() {
@@ -1650,6 +1646,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1650
1646
  this._secrets = void 0;
1651
1647
  this._spaceService = void 0;
1652
1648
  this._serviceContext = void 0;
1649
+ this.runtimePermissionGrants = [];
1653
1650
  await this.tc.signIn(options);
1654
1651
  this.syncResolvedHostFromAuth();
1655
1652
  this.initializeServices();
@@ -1739,6 +1736,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1739
1736
  this._secrets = void 0;
1740
1737
  this._spaceService = void 0;
1741
1738
  this._serviceContext = void 0;
1739
+ this.runtimePermissionGrants = [];
1742
1740
  if (sessionData.address) {
1743
1741
  this._address = sessionData.address;
1744
1742
  }
@@ -1746,8 +1744,8 @@ var _TinyCloudNode = class _TinyCloudNode {
1746
1744
  this._chainId = sessionData.chainId;
1747
1745
  }
1748
1746
  this._serviceContext = new ServiceContext2({
1749
- invoke: this.wasmBindings.invoke,
1750
- invokeAny: this.wasmBindings.invokeAny,
1747
+ invoke: this.invokeWithRuntimePermissions,
1748
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1751
1749
  fetch: globalThis.fetch.bind(globalThis),
1752
1750
  hosts: [this.config.host]
1753
1751
  });
@@ -1833,7 +1831,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1833
1831
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1834
1832
  });
1835
1833
  this.tc = new TinyCloud(this.auth, {
1836
- invokeAny: this.wasmBindings.invokeAny
1834
+ invokeAny: this.invokeAnyWithRuntimePermissions
1837
1835
  });
1838
1836
  this.config.prefix = prefix;
1839
1837
  }
@@ -1877,7 +1875,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1877
1875
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1878
1876
  });
1879
1877
  this.tc = new TinyCloud(this.auth, {
1880
- invokeAny: this.wasmBindings.invokeAny
1878
+ invokeAny: this.invokeAnyWithRuntimePermissions
1881
1879
  });
1882
1880
  this.config.prefix = prefix;
1883
1881
  }
@@ -1890,10 +1888,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1890
1888
  if (!session) {
1891
1889
  return;
1892
1890
  }
1893
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
1891
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
1894
1892
  this._serviceContext = new ServiceContext2({
1895
- invoke: this.wasmBindings.invoke,
1896
- invokeAny: this.wasmBindings.invokeAny,
1893
+ invoke: this.invokeWithRuntimePermissions,
1894
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1897
1895
  fetch: globalThis.fetch.bind(globalThis),
1898
1896
  hosts: [this.config.host]
1899
1897
  });
@@ -2063,7 +2061,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2063
2061
  this._delegationManager = new DelegationManager({
2064
2062
  hosts: [this.config.host],
2065
2063
  session: serviceSession,
2066
- invoke: this.wasmBindings.invoke,
2064
+ invoke: this.invokeWithRuntimePermissions,
2067
2065
  fetch: globalThis.fetch.bind(globalThis)
2068
2066
  });
2069
2067
  this._spaceService = new SpaceService({
@@ -2330,9 +2328,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2330
2328
  this._secrets = new NodeSecretsService({
2331
2329
  getService: () => this.getBaseSecrets(),
2332
2330
  getManifest: () => this.manifest,
2333
- setManifest: (manifest) => this.setManifest(manifest),
2334
- signIn: () => this.signIn(),
2335
- canEscalate: () => this.signer !== void 0 && this.tc !== void 0
2331
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2332
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2333
+ getUnlockSigner: () => this.signer ?? void 0
2336
2334
  });
2337
2335
  }
2338
2336
  return this._secrets;
@@ -2416,6 +2414,171 @@ var _TinyCloudNode = class _TinyCloudNode {
2416
2414
  }
2417
2415
  };
2418
2416
  }
2417
+ /**
2418
+ * Check whether the current session or an approved runtime delegation covers
2419
+ * every requested permission.
2420
+ */
2421
+ hasRuntimePermissions(permissions) {
2422
+ const session = this.auth?.tinyCloudSession;
2423
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2424
+ return false;
2425
+ }
2426
+ const expanded = this.expandPermissionEntries(permissions);
2427
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2428
+ return true;
2429
+ }
2430
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
2431
+ }
2432
+ /**
2433
+ * Return installed runtime permission delegations. When `permissions` is
2434
+ * provided, only delegations currently covering those permissions are
2435
+ * returned. Base-session manifest permissions are not represented here.
2436
+ */
2437
+ getRuntimePermissionDelegations(permissions) {
2438
+ this.pruneExpiredRuntimePermissionGrants();
2439
+ if (permissions === void 0) {
2440
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
2441
+ }
2442
+ const session = this.auth?.tinyCloudSession;
2443
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2444
+ return [];
2445
+ }
2446
+ const expanded = this.expandPermissionEntries(permissions);
2447
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
2448
+ (grant) => grant.delegation
2449
+ );
2450
+ }
2451
+ /**
2452
+ * Install a portable runtime permission delegation into this SDK instance so
2453
+ * matching service calls and downstream `delegateTo()` calls can use it.
2454
+ */
2455
+ async useRuntimeDelegation(delegation) {
2456
+ const session = this.auth?.tinyCloudSession;
2457
+ if (!session) {
2458
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2459
+ }
2460
+ if (delegation.expiry.getTime() <= Date.now()) {
2461
+ throw new SessionExpiredError(delegation.expiry);
2462
+ }
2463
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
2464
+ if (!expectedDids.has(delegation.delegateDID)) {
2465
+ throw new Error(
2466
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
2467
+ );
2468
+ }
2469
+ const targetHost = delegation.host ?? this.config.host;
2470
+ const activateResult = await activateSessionWithHost2(
2471
+ targetHost,
2472
+ delegation.delegationHeader
2473
+ );
2474
+ if (!activateResult.success) {
2475
+ throw new Error(
2476
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2477
+ );
2478
+ }
2479
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
2480
+ (grant) => grant.delegation.cid !== delegation.cid
2481
+ );
2482
+ this.runtimePermissionGrants.push(
2483
+ this.runtimeGrantFromDelegation(delegation, session)
2484
+ );
2485
+ }
2486
+ /**
2487
+ * Store additional permissions as narrow delegations to the current session
2488
+ * key. Future service invocations automatically use a stored delegation when
2489
+ * its `(space, service, path, action)` covers the request.
2490
+ */
2491
+ async grantRuntimePermissions(permissions, options) {
2492
+ if (!Array.isArray(permissions) || permissions.length === 0) {
2493
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2494
+ }
2495
+ const session = this.auth?.tinyCloudSession;
2496
+ if (!session) {
2497
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2498
+ }
2499
+ const sessionExpiry = extractSiweExpiration(session.siwe);
2500
+ if (sessionExpiry !== void 0) {
2501
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2502
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
2503
+ throw new SessionExpiredError(sessionExpiry);
2504
+ }
2505
+ }
2506
+ const expanded = this.expandPermissionEntries(permissions);
2507
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2508
+ return [];
2509
+ }
2510
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
2511
+ if (existingGrants.length > 0) {
2512
+ return existingGrants.map((grant) => grant.delegation);
2513
+ }
2514
+ if (!this.signer) {
2515
+ throw new Error(
2516
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2517
+ );
2518
+ }
2519
+ const bySpace = /* @__PURE__ */ new Map();
2520
+ for (const entry of expanded) {
2521
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
2522
+ const current = bySpace.get(spaceId) ?? [];
2523
+ current.push(entry);
2524
+ bySpace.set(spaceId, current);
2525
+ }
2526
+ const now = /* @__PURE__ */ new Date();
2527
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2528
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
2529
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
2530
+ expiresAt = sessionExpiry;
2531
+ }
2532
+ const delegations = [];
2533
+ for (const [spaceId, entries] of bySpace) {
2534
+ const abilities = this.permissionsToAbilities(entries);
2535
+ const prepared = this.wasmBindings.prepareSession({
2536
+ abilities,
2537
+ address: this.wasmBindings.ensureEip55(session.address),
2538
+ chainId: session.chainId,
2539
+ domain: this.siweDomain,
2540
+ issuedAt: now.toISOString(),
2541
+ expirationTime: expiresAt.toISOString(),
2542
+ spaceId,
2543
+ jwk: session.jwk
2544
+ });
2545
+ const signature = await this.signer.signMessage(prepared.siwe);
2546
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
2547
+ ...prepared,
2548
+ signature
2549
+ });
2550
+ const activateResult = await activateSessionWithHost2(
2551
+ this.config.host,
2552
+ delegatedSession.delegationHeader
2553
+ );
2554
+ if (!activateResult.success) {
2555
+ throw new Error(
2556
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2557
+ );
2558
+ }
2559
+ const delegation = this.runtimeDelegationFromSession(
2560
+ delegatedSession,
2561
+ entries,
2562
+ spaceId,
2563
+ session,
2564
+ expiresAt
2565
+ );
2566
+ this.runtimePermissionGrants.push({
2567
+ session: {
2568
+ delegationHeader: delegatedSession.delegationHeader,
2569
+ delegationCid: delegatedSession.delegationCid,
2570
+ spaceId,
2571
+ verificationMethod: session.verificationMethod,
2572
+ jwk: session.jwk
2573
+ },
2574
+ delegation,
2575
+ operations: this.permissionOperations(entries, spaceId),
2576
+ expiresAt
2577
+ });
2578
+ delegations.push(delegation);
2579
+ }
2580
+ return delegations;
2581
+ }
2419
2582
  /**
2420
2583
  * Get the DelegationManager for delegation CRUD operations.
2421
2584
  *
@@ -2604,7 +2767,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2604
2767
  if (this._serviceContext) {
2605
2768
  const publicKV = new KVService2({ prefix: "" });
2606
2769
  const publicContext = new ServiceContext2({
2607
- invoke: this.wasmBindings.invoke,
2770
+ invoke: this.invokeWithRuntimePermissions,
2608
2771
  fetch: this._serviceContext.fetch,
2609
2772
  hosts: this._serviceContext.hosts
2610
2773
  });
@@ -2692,8 +2855,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2692
2855
  * Issue a delegation using the capability-chain flow.
2693
2856
  *
2694
2857
  * When every requested permission is a subset of the current
2695
- * session's recap, the delegation is signed by the session key via
2696
- * WASM no wallet prompt. When at least one is NOT derivable, a
2858
+ * session's recap, or of one installed runtime permission delegation,
2859
+ * the delegation is signed by the session key via WASM no wallet
2860
+ * prompt. When at least one is NOT derivable, a
2697
2861
  * {@link PermissionNotInManifestError} is raised (carrying the
2698
2862
  * missing entries) so the caller can trigger an escalation flow
2699
2863
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -2773,6 +2937,23 @@ var _TinyCloudNode = class _TinyCloudNode {
2773
2937
  );
2774
2938
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
2775
2939
  if (!subset) {
2940
+ const runtimeGrant = this.findGrantForOperations(
2941
+ this.permissionEntriesToOperations(expandedEntries, session)
2942
+ );
2943
+ if (runtimeGrant) {
2944
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2945
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
2946
+ throw new SessionExpiredError(runtimeGrant.expiresAt);
2947
+ }
2948
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
2949
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
2950
+ did,
2951
+ expandedEntries,
2952
+ runtimeExpiration,
2953
+ runtimeGrant
2954
+ );
2955
+ return { delegation: delegation2, prompted: false };
2956
+ }
2776
2957
  throw new PermissionNotInManifestError(missing, granted);
2777
2958
  }
2778
2959
  const delegation = await this.createDelegationViaWasmPath(
@@ -2917,6 +3098,41 @@ var _TinyCloudNode = class _TinyCloudNode {
2917
3098
  host: this.config.host
2918
3099
  };
2919
3100
  }
3101
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
3102
+ const result = this.createDelegationWrapper({
3103
+ session: grant.session,
3104
+ delegateDID: did,
3105
+ spaceId: grant.session.spaceId,
3106
+ abilities: this.permissionsToAbilities(entries),
3107
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
3108
+ });
3109
+ const primary = result.resources[0];
3110
+ const delegationHeader = { Authorization: result.delegation };
3111
+ const targetHost = grant.delegation.host ?? this.config.host;
3112
+ const activateResult = await activateSessionWithHost2(
3113
+ targetHost,
3114
+ delegationHeader
3115
+ );
3116
+ if (!activateResult.success) {
3117
+ throw new Error(
3118
+ `Failed to activate delegation with host: ${activateResult.error}`
3119
+ );
3120
+ }
3121
+ return {
3122
+ cid: result.cid,
3123
+ delegationHeader,
3124
+ spaceId: grant.session.spaceId,
3125
+ path: primary.path,
3126
+ actions: primary.actions,
3127
+ resources: result.resources,
3128
+ disableSubDelegation: false,
3129
+ expiry: result.expiry,
3130
+ delegateDID: did,
3131
+ ownerAddress: grant.delegation.ownerAddress,
3132
+ chainId: grant.delegation.chainId,
3133
+ host: targetHost
3134
+ };
3135
+ }
2920
3136
  resolvePermissionSpace(space, session) {
2921
3137
  if (space === void 0) {
2922
3138
  return this.wasmBindings.makeSpaceId(
@@ -2933,6 +3149,223 @@ var _TinyCloudNode = class _TinyCloudNode {
2933
3149
  }
2934
3150
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
2935
3151
  }
3152
+ expandPermissionEntries(permissions) {
3153
+ return permissions.map((entry) => ({
3154
+ ...entry,
3155
+ actions: expandActionShortNames(entry.service, entry.actions)
3156
+ }));
3157
+ }
3158
+ shortServiceName(service) {
3159
+ const short = SERVICE_LONG_TO_SHORT[service];
3160
+ if (short === void 0) {
3161
+ throw new Error(
3162
+ `unknown service '${service}' \u2014 no short-form mapping`
3163
+ );
3164
+ }
3165
+ return short;
3166
+ }
3167
+ permissionsToAbilities(entries) {
3168
+ const abilities = {};
3169
+ for (const entry of entries) {
3170
+ const service = this.shortServiceName(entry.service);
3171
+ abilities[service] ?? (abilities[service] = {});
3172
+ const existing = abilities[service][entry.path] ?? [];
3173
+ const seen = new Set(existing);
3174
+ for (const action of entry.actions) {
3175
+ if (!seen.has(action)) {
3176
+ existing.push(action);
3177
+ seen.add(action);
3178
+ }
3179
+ }
3180
+ abilities[service][entry.path] = existing;
3181
+ }
3182
+ return abilities;
3183
+ }
3184
+ permissionOperations(entries, spaceId) {
3185
+ return entries.flatMap((entry) => {
3186
+ const service = this.shortServiceName(entry.service);
3187
+ return entry.actions.map((action) => ({
3188
+ spaceId,
3189
+ service,
3190
+ path: entry.path,
3191
+ action
3192
+ }));
3193
+ });
3194
+ }
3195
+ sessionCoversPermissionEntries(session, entries) {
3196
+ try {
3197
+ const granted = parseRecapCapabilities(
3198
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
3199
+ session.siwe
3200
+ );
3201
+ return isCapabilitySubset(entries, granted).subset;
3202
+ } catch {
3203
+ return false;
3204
+ }
3205
+ }
3206
+ permissionEntriesToOperations(entries, session) {
3207
+ return entries.flatMap((entry) => {
3208
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
3209
+ const service = this.shortServiceName(entry.service);
3210
+ return entry.actions.map((action) => ({
3211
+ spaceId,
3212
+ service,
3213
+ path: entry.path,
3214
+ action
3215
+ }));
3216
+ });
3217
+ }
3218
+ findRuntimeGrantsForPermissionEntries(entries, session) {
3219
+ const grants = [];
3220
+ const operations = this.permissionEntriesToOperations(entries, session);
3221
+ if (operations.length === 0) {
3222
+ return grants;
3223
+ }
3224
+ for (const operation of operations) {
3225
+ const grant = this.findGrantForOperation(operation);
3226
+ if (!grant) {
3227
+ return [];
3228
+ }
3229
+ if (!grants.includes(grant)) {
3230
+ grants.push(grant);
3231
+ }
3232
+ }
3233
+ return grants;
3234
+ }
3235
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
3236
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
3237
+ const primary = resources[0];
3238
+ return {
3239
+ cid: delegatedSession.delegationCid,
3240
+ delegationHeader: delegatedSession.delegationHeader,
3241
+ spaceId,
3242
+ path: primary.path,
3243
+ actions: primary.actions,
3244
+ resources,
3245
+ disableSubDelegation: false,
3246
+ expiry: expiresAt,
3247
+ delegateDID: session.verificationMethod,
3248
+ ownerAddress: session.address,
3249
+ chainId: session.chainId,
3250
+ host: this.config.host
3251
+ };
3252
+ }
3253
+ runtimeGrantFromDelegation(delegation, session) {
3254
+ const operations = this.operationsFromDelegation(delegation);
3255
+ return {
3256
+ session: {
3257
+ delegationHeader: delegation.delegationHeader,
3258
+ delegationCid: delegation.cid,
3259
+ spaceId: delegation.spaceId,
3260
+ verificationMethod: session.verificationMethod,
3261
+ jwk: session.jwk
3262
+ },
3263
+ delegation,
3264
+ operations,
3265
+ expiresAt: delegation.expiry
3266
+ };
3267
+ }
3268
+ delegatedResourcesForEntries(entries, spaceId) {
3269
+ return entries.map((entry) => ({
3270
+ service: this.shortServiceName(entry.service),
3271
+ space: spaceId,
3272
+ path: entry.path,
3273
+ actions: [...entry.actions]
3274
+ }));
3275
+ }
3276
+ operationsFromDelegation(delegation) {
3277
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3278
+ return resources.flatMap(
3279
+ (resource) => resource.actions.map((action) => ({
3280
+ spaceId: resource.space,
3281
+ service: this.invocationServiceName(resource.service),
3282
+ path: resource.path,
3283
+ action
3284
+ }))
3285
+ );
3286
+ }
3287
+ flatDelegationResources(delegation) {
3288
+ const byService = /* @__PURE__ */ new Map();
3289
+ for (const action of delegation.actions) {
3290
+ const service = this.shortServiceName(action.split("/")[0]);
3291
+ const actions = byService.get(service) ?? [];
3292
+ actions.push(action);
3293
+ byService.set(service, actions);
3294
+ }
3295
+ return [...byService.entries()].map(([service, actions]) => ({
3296
+ service,
3297
+ space: delegation.spaceId,
3298
+ path: delegation.path,
3299
+ actions
3300
+ }));
3301
+ }
3302
+ selectInvocationSession(fallback, service, path, action) {
3303
+ const grant = this.findGrantForOperation({
3304
+ spaceId: fallback.spaceId,
3305
+ service: this.invocationServiceName(service),
3306
+ path,
3307
+ action
3308
+ });
3309
+ return grant?.session ?? fallback;
3310
+ }
3311
+ findGrantForOperations(operations) {
3312
+ if (operations.length === 0) {
3313
+ return void 0;
3314
+ }
3315
+ this.pruneExpiredRuntimePermissionGrants();
3316
+ return this.runtimePermissionGrants.find((grant) => {
3317
+ return operations.every(
3318
+ (operation) => grant.operations.some(
3319
+ (granted) => this.operationCovers(granted, operation)
3320
+ )
3321
+ );
3322
+ });
3323
+ }
3324
+ findGrantForOperation(operation) {
3325
+ return this.findGrantForOperations([operation]);
3326
+ }
3327
+ pruneExpiredRuntimePermissionGrants() {
3328
+ const now = Date.now();
3329
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3330
+ (grant) => grant.expiresAt.getTime() > now
3331
+ );
3332
+ }
3333
+ operationCovers(granted, requested) {
3334
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3335
+ }
3336
+ actionContains(grantedAction, requestedAction) {
3337
+ if (grantedAction === requestedAction) {
3338
+ return true;
3339
+ }
3340
+ if (grantedAction.endsWith("/*")) {
3341
+ const prefix = grantedAction.slice(0, -2);
3342
+ return requestedAction.startsWith(`${prefix}/`);
3343
+ }
3344
+ return false;
3345
+ }
3346
+ invocationServiceName(service) {
3347
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3348
+ }
3349
+ pathContains(grantedPath, requestedPath) {
3350
+ if (grantedPath === "" || grantedPath === "/") {
3351
+ return true;
3352
+ }
3353
+ if (grantedPath.endsWith("/**")) {
3354
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
3355
+ }
3356
+ if (grantedPath.endsWith("/*")) {
3357
+ const prefix = grantedPath.slice(0, -2);
3358
+ if (!requestedPath.startsWith(prefix)) {
3359
+ return false;
3360
+ }
3361
+ const remainder = requestedPath.slice(prefix.length);
3362
+ return !remainder.includes("/") || remainder === "/";
3363
+ }
3364
+ if (grantedPath.endsWith("/")) {
3365
+ return requestedPath.startsWith(grantedPath);
3366
+ }
3367
+ return grantedPath === requestedPath;
3368
+ }
2936
3369
  /**
2937
3370
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
2938
3371
  * {@link PermissionEntry}. Shares the implementation with the public