@tinycloud/node-sdk 2.5.1 → 2.6.0-beta.1

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.
@@ -910,6 +910,12 @@ declare class TinyCloudNode {
910
910
  private _delegationManager?;
911
911
  private _spaceService?;
912
912
  private runtimePermissionGrants;
913
+ /**
914
+ * Memoized `recapOperationsFromSession` result, keyed by the exact SIWE it
915
+ * was parsed from. The primary session is stable for the life of a sign-in,
916
+ * so this avoids re-parsing the recap on every registration.
917
+ */
918
+ private _recapOperationsCache?;
913
919
  /**
914
920
  * TinyCloudSession captured by {@link restoreSession} when there's no
915
921
  * auth-layer signer available (session-only mode used by OpenKey-backed
@@ -1052,6 +1058,42 @@ declare class TinyCloudNode {
1052
1058
  private isFreshBootstrapAccount;
1053
1059
  private runAccountBootstrap;
1054
1060
  private registerBootstrapRuntimeGrant;
1061
+ /**
1062
+ * Map the base session's OWN recap into runtime permission operations.
1063
+ *
1064
+ * Uses the RAW `parseRecapFromSiwe` binding — NOT `parseRecapCapabilities`,
1065
+ * whose `normalizeSpace` collapses `tinycloud:pkh:...:<owner>:<space>` to a
1066
+ * bare short name and would conflate two owners' identically-named spaces.
1067
+ * We must keep the full owner-scoped URI so a synthetic primary grant can
1068
+ * never cover an operation on a different owner's space.
1069
+ *
1070
+ * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
1071
+ * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
1072
+ * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
1073
+ * One op per action.
1074
+ *
1075
+ * Returns `[]` for session-only / restored-without-siwe modes and for any
1076
+ * unparseable SIWE — the primary grant is simply not registered in that case.
1077
+ */
1078
+ private recapOperationsFromSession;
1079
+ /**
1080
+ * Register the base (primary) session's own recap as a synthetic runtime
1081
+ * grant tagged `provenance: "primary"` so it always out-ranks other covering
1082
+ * grants in {@link findGrantForOperations}. This closes the selection-design
1083
+ * hazard where a broad — possibly broken — bootstrap/delegated grant could
1084
+ * hijack an operation the primary session itself already authorized (TC-111).
1085
+ *
1086
+ * Two safety exclusions:
1087
+ * - Ops whose space is in `lastActivationSkippedSpaceIds` are dropped: the
1088
+ * node refused to activate those spaces this sign-in even though the recap
1089
+ * claims them. Including them would let the synthetic primary out-rank a
1090
+ * working grant and 401 (the "skipped-activation inverted hijack").
1091
+ * - Encryption `resource` ops are kept as-is (space-independent).
1092
+ *
1093
+ * No-ops when nothing remains after exclusion. Callers (`signIn`,
1094
+ * `restoreSession`) clear `runtimePermissionGrants` first, so no dupes.
1095
+ */
1096
+ private registerPrimarySessionGrant;
1055
1097
  private writeManifestRegistryRecords;
1056
1098
  private scheduleAccountRegistrySync;
1057
1099
  private withAccountRegistryRetry;
@@ -910,6 +910,12 @@ declare class TinyCloudNode {
910
910
  private _delegationManager?;
911
911
  private _spaceService?;
912
912
  private runtimePermissionGrants;
913
+ /**
914
+ * Memoized `recapOperationsFromSession` result, keyed by the exact SIWE it
915
+ * was parsed from. The primary session is stable for the life of a sign-in,
916
+ * so this avoids re-parsing the recap on every registration.
917
+ */
918
+ private _recapOperationsCache?;
913
919
  /**
914
920
  * TinyCloudSession captured by {@link restoreSession} when there's no
915
921
  * auth-layer signer available (session-only mode used by OpenKey-backed
@@ -1052,6 +1058,42 @@ declare class TinyCloudNode {
1052
1058
  private isFreshBootstrapAccount;
1053
1059
  private runAccountBootstrap;
1054
1060
  private registerBootstrapRuntimeGrant;
1061
+ /**
1062
+ * Map the base session's OWN recap into runtime permission operations.
1063
+ *
1064
+ * Uses the RAW `parseRecapFromSiwe` binding — NOT `parseRecapCapabilities`,
1065
+ * whose `normalizeSpace` collapses `tinycloud:pkh:...:<owner>:<space>` to a
1066
+ * bare short name and would conflate two owners' identically-named spaces.
1067
+ * We must keep the full owner-scoped URI so a synthetic primary grant can
1068
+ * never cover an operation on a different owner's space.
1069
+ *
1070
+ * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
1071
+ * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
1072
+ * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
1073
+ * One op per action.
1074
+ *
1075
+ * Returns `[]` for session-only / restored-without-siwe modes and for any
1076
+ * unparseable SIWE — the primary grant is simply not registered in that case.
1077
+ */
1078
+ private recapOperationsFromSession;
1079
+ /**
1080
+ * Register the base (primary) session's own recap as a synthetic runtime
1081
+ * grant tagged `provenance: "primary"` so it always out-ranks other covering
1082
+ * grants in {@link findGrantForOperations}. This closes the selection-design
1083
+ * hazard where a broad — possibly broken — bootstrap/delegated grant could
1084
+ * hijack an operation the primary session itself already authorized (TC-111).
1085
+ *
1086
+ * Two safety exclusions:
1087
+ * - Ops whose space is in `lastActivationSkippedSpaceIds` are dropped: the
1088
+ * node refused to activate those spaces this sign-in even though the recap
1089
+ * claims them. Including them would let the synthetic primary out-rank a
1090
+ * working grant and 401 (the "skipped-activation inverted hijack").
1091
+ * - Encryption `resource` ops are kept as-is (space-independent).
1092
+ *
1093
+ * No-ops when nothing remains after exclusion. Callers (`signIn`,
1094
+ * `restoreSession`) clear `runtimePermissionGrants` first, so no dupes.
1095
+ */
1096
+ private registerPrimarySessionGrant;
1055
1097
  private writeManifestRegistryRecords;
1056
1098
  private scheduleAccountRegistrySync;
1057
1099
  private withAccountRegistryRetry;
package/dist/core.cjs CHANGED
@@ -327,8 +327,8 @@ var import_sdk_core2 = require("@tinycloud/sdk-core");
327
327
  var defaultSignStrategy = { type: "auto-sign" };
328
328
 
329
329
  // src/authorization/NodeUserAuthorization.ts
330
- var DECRYPT_ACTION = "tinycloud.encryption/decrypt";
331
- var NETWORK_CREATE_ACTION = "tinycloud.encryption/network.create";
330
+ var DECRYPT_ACTION = import_sdk_core3.ENCRYPTION.DECRYPT;
331
+ var NETWORK_CREATE_ACTION = import_sdk_core3.ENCRYPTION.NETWORK_CREATE;
332
332
  function didPrincipalMatches(actual, expected) {
333
333
  try {
334
334
  return (0, import_sdk_core3.principalDidEquals)(actual, expected);
@@ -361,44 +361,27 @@ var NodeUserAuthorization = class {
361
361
  this.spacePrefix = config.spacePrefix ?? "default";
362
362
  this.defaultActions = config.defaultActions ?? {
363
363
  kv: {
364
- "": [
365
- "tinycloud.kv/put",
366
- "tinycloud.kv/get",
367
- "tinycloud.kv/del",
368
- "tinycloud.kv/list",
369
- "tinycloud.kv/metadata"
370
- ]
364
+ "": [import_sdk_core3.KV.PUT, import_sdk_core3.KV.GET, import_sdk_core3.KV.DEL, import_sdk_core3.KV.LIST, import_sdk_core3.KV.METADATA]
371
365
  },
372
366
  sql: {
373
- "": [
374
- "tinycloud.sql/read",
375
- "tinycloud.sql/write",
376
- "tinycloud.sql/schema",
377
- "tinycloud.sql/admin",
378
- "tinycloud.sql/export"
379
- ]
367
+ "": [import_sdk_core3.SQL.READ, import_sdk_core3.SQL.WRITE, import_sdk_core3.SQL.SCHEMA, import_sdk_core3.SQL.ADMIN, import_sdk_core3.SQL.EXPORT]
380
368
  },
381
369
  duckdb: {
382
370
  "": [
383
- "tinycloud.duckdb/read",
384
- "tinycloud.duckdb/write",
385
- "tinycloud.duckdb/admin",
386
- "tinycloud.duckdb/describe",
387
- "tinycloud.duckdb/export",
388
- "tinycloud.duckdb/import",
389
- "tinycloud.duckdb/execute"
371
+ import_sdk_core3.DUCKDB.READ,
372
+ import_sdk_core3.DUCKDB.WRITE,
373
+ import_sdk_core3.DUCKDB.ADMIN,
374
+ import_sdk_core3.DUCKDB.DESCRIBE,
375
+ import_sdk_core3.DUCKDB.EXPORT,
376
+ import_sdk_core3.DUCKDB.IMPORT,
377
+ import_sdk_core3.DUCKDB.EXECUTE
390
378
  ]
391
379
  },
392
380
  capabilities: {
393
- "": ["tinycloud.capabilities/read"]
381
+ "": [import_sdk_core3.CAPABILITIES.READ]
394
382
  },
395
383
  hooks: {
396
- "": [
397
- "tinycloud.hooks/subscribe",
398
- "tinycloud.hooks/register",
399
- "tinycloud.hooks/list",
400
- "tinycloud.hooks/unregister"
401
- ]
384
+ "": [import_sdk_core3.HOOKS.SUBSCRIBE, import_sdk_core3.HOOKS.REGISTER, import_sdk_core3.HOOKS.LIST, import_sdk_core3.HOOKS.UNREGISTER]
402
385
  }
403
386
  };
404
387
  this.sessionExpirationMs = config.sessionExpirationMs ?? import_sdk_core3.EXPIRY.SESSION_MS;
@@ -598,20 +581,17 @@ var NodeUserAuthorization = class {
598
581
  [secretsSpaceId]: {
599
582
  kv: {
600
583
  "vault/secrets/": [
601
- "tinycloud.kv/get",
602
- "tinycloud.kv/put",
603
- "tinycloud.kv/del",
604
- "tinycloud.kv/list",
605
- "tinycloud.kv/metadata"
584
+ import_sdk_core3.KV.GET,
585
+ import_sdk_core3.KV.PUT,
586
+ import_sdk_core3.KV.DEL,
587
+ import_sdk_core3.KV.LIST,
588
+ import_sdk_core3.KV.METADATA
606
589
  ]
607
590
  }
608
591
  }
609
592
  },
610
593
  rawAbilities: {
611
- [defaultNetworkId]: [
612
- "tinycloud.encryption/decrypt",
613
- "tinycloud.encryption/network.create"
614
- ]
594
+ [defaultNetworkId]: [DECRYPT_ACTION, NETWORK_CREATE_ACTION]
615
595
  }
616
596
  };
617
597
  }
@@ -1675,9 +1655,27 @@ var NodeSecretsService = class {
1675
1655
  // src/TinyCloudNode.ts
1676
1656
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
1677
1657
  var DEFAULT_ENCRYPTION_NETWORK_NAME = "default";
1678
- var NETWORK_CREATE_ACTION2 = "tinycloud.encryption/network.create";
1679
- var DECRYPT_ACTION2 = "tinycloud.encryption/decrypt";
1658
+ var NETWORK_CREATE_ACTION2 = import_sdk_core8.ENCRYPTION.NETWORK_CREATE;
1659
+ var DECRYPT_ACTION2 = import_sdk_core8.ENCRYPTION.DECRYPT;
1680
1660
  var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
1661
+ var ROOT_DELEGATION_ACTIONS = [
1662
+ import_sdk_core8.KV.PUT,
1663
+ import_sdk_core8.KV.GET,
1664
+ import_sdk_core8.KV.DEL,
1665
+ import_sdk_core8.KV.LIST,
1666
+ import_sdk_core8.KV.METADATA,
1667
+ import_sdk_core8.SQL.READ,
1668
+ import_sdk_core8.SQL.WRITE,
1669
+ import_sdk_core8.SQL.ADMIN,
1670
+ import_sdk_core8.SQL.ALL,
1671
+ import_sdk_core8.DUCKDB.READ,
1672
+ import_sdk_core8.DUCKDB.WRITE,
1673
+ import_sdk_core8.DUCKDB.ADMIN,
1674
+ import_sdk_core8.DUCKDB.DESCRIBE,
1675
+ import_sdk_core8.DUCKDB.EXPORT,
1676
+ import_sdk_core8.DUCKDB.IMPORT,
1677
+ import_sdk_core8.DUCKDB.ALL
1678
+ ];
1681
1679
  var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core8.EXPIRY.SESSION_MS;
1682
1680
  function isOpenKeyAutoSignStrategy(strategy) {
1683
1681
  return strategy?.openKeyAutoSign === true;
@@ -2149,6 +2147,10 @@ var _TinyCloudNode = class _TinyCloudNode {
2149
2147
  await this.tc.signIn(options);
2150
2148
  this.syncResolvedHostFromAuth();
2151
2149
  this.initializeServices();
2150
+ const primarySession = this.currentTinyCloudSession();
2151
+ if (primarySession) {
2152
+ this.registerPrimarySessionGrant(primarySession);
2153
+ }
2152
2154
  const bootstrapped = await this.bootstrapAccountIfNeeded();
2153
2155
  await this.ensureRequestedEncryptionNetworks();
2154
2156
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
@@ -2392,7 +2394,107 @@ var _TinyCloudNode = class _TinyCloudNode {
2392
2394
  host: this.config.host
2393
2395
  },
2394
2396
  operations,
2395
- expiresAt
2397
+ expiresAt,
2398
+ provenance: "bootstrap"
2399
+ });
2400
+ }
2401
+ /**
2402
+ * Map the base session's OWN recap into runtime permission operations.
2403
+ *
2404
+ * Uses the RAW `parseRecapFromSiwe` binding — NOT `parseRecapCapabilities`,
2405
+ * whose `normalizeSpace` collapses `tinycloud:pkh:...:<owner>:<space>` to a
2406
+ * bare short name and would conflate two owners' identically-named spaces.
2407
+ * We must keep the full owner-scoped URI so a synthetic primary grant can
2408
+ * never cover an operation on a different owner's space.
2409
+ *
2410
+ * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
2411
+ * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
2412
+ * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
2413
+ * One op per action.
2414
+ *
2415
+ * Returns `[]` for session-only / restored-without-siwe modes and for any
2416
+ * unparseable SIWE — the primary grant is simply not registered in that case.
2417
+ */
2418
+ recapOperationsFromSession(session) {
2419
+ const siwe = session.siwe;
2420
+ if (!siwe) {
2421
+ return [];
2422
+ }
2423
+ if (this._recapOperationsCache?.siwe === siwe) {
2424
+ return this._recapOperationsCache.operations;
2425
+ }
2426
+ let operations = [];
2427
+ try {
2428
+ const entries = this.wasmBindings.parseRecapFromSiwe(siwe);
2429
+ if (Array.isArray(entries)) {
2430
+ operations = entries.flatMap((entry) => {
2431
+ const service = this.invocationServiceName(entry.service);
2432
+ return entry.actions.map((action) => ({
2433
+ ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
2434
+ service,
2435
+ path: entry.path,
2436
+ action
2437
+ }));
2438
+ });
2439
+ }
2440
+ } catch {
2441
+ operations = [];
2442
+ }
2443
+ this._recapOperationsCache = { siwe, operations };
2444
+ return operations;
2445
+ }
2446
+ /**
2447
+ * Register the base (primary) session's own recap as a synthetic runtime
2448
+ * grant tagged `provenance: "primary"` so it always out-ranks other covering
2449
+ * grants in {@link findGrantForOperations}. This closes the selection-design
2450
+ * hazard where a broad — possibly broken — bootstrap/delegated grant could
2451
+ * hijack an operation the primary session itself already authorized (TC-111).
2452
+ *
2453
+ * Two safety exclusions:
2454
+ * - Ops whose space is in `lastActivationSkippedSpaceIds` are dropped: the
2455
+ * node refused to activate those spaces this sign-in even though the recap
2456
+ * claims them. Including them would let the synthetic primary out-rank a
2457
+ * working grant and 401 (the "skipped-activation inverted hijack").
2458
+ * - Encryption `resource` ops are kept as-is (space-independent).
2459
+ *
2460
+ * No-ops when nothing remains after exclusion. Callers (`signIn`,
2461
+ * `restoreSession`) clear `runtimePermissionGrants` first, so no dupes.
2462
+ */
2463
+ registerPrimarySessionGrant(session) {
2464
+ const skipped = this.auth ? this.auth.lastActivationSkippedSpaceIds ?? [] : [];
2465
+ const operations = this.recapOperationsFromSession(session).filter(
2466
+ (operation) => operation.spaceId === void 0 || !skipped.some((spaceId) => this.spaceIdsEqual(spaceId, operation.spaceId))
2467
+ );
2468
+ if (operations.length === 0) {
2469
+ return;
2470
+ }
2471
+ const expiresAt = extractSiweExpiration(session.siwe) ?? this.getSessionExpiry();
2472
+ const actions = [...new Set(operations.map((operation) => operation.action))];
2473
+ this.runtimePermissionGrants.push({
2474
+ session: {
2475
+ delegationHeader: session.delegationHeader,
2476
+ delegationCid: session.delegationCid,
2477
+ spaceId: session.spaceId,
2478
+ verificationMethod: session.verificationMethod,
2479
+ jwk: session.jwk
2480
+ },
2481
+ delegation: {
2482
+ cid: session.delegationCid,
2483
+ delegationHeader: session.delegationHeader,
2484
+ delegateDID: session.verificationMethod,
2485
+ delegatorDID: this.did,
2486
+ spaceId: session.spaceId,
2487
+ path: "",
2488
+ actions,
2489
+ expiry: expiresAt,
2490
+ allowSubDelegation: true,
2491
+ ownerAddress: session.address,
2492
+ chainId: session.chainId,
2493
+ host: this.config.host
2494
+ },
2495
+ operations,
2496
+ expiresAt,
2497
+ provenance: "primary"
2396
2498
  });
2397
2499
  }
2398
2500
  async writeManifestRegistryRecords() {
@@ -2728,6 +2830,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2728
2830
  } else {
2729
2831
  this._restoredTcSession = tcSession;
2730
2832
  }
2833
+ this.registerPrimarySessionGrant(tcSession);
2731
2834
  }
2732
2835
  }
2733
2836
  /**
@@ -3199,24 +3302,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3199
3302
  spaceId: tcSession.spaceId,
3200
3303
  path: "",
3201
3304
  // Root access
3202
- actions: [
3203
- "tinycloud.kv/put",
3204
- "tinycloud.kv/get",
3205
- "tinycloud.kv/del",
3206
- "tinycloud.kv/list",
3207
- "tinycloud.kv/metadata",
3208
- "tinycloud.sql/read",
3209
- "tinycloud.sql/write",
3210
- "tinycloud.sql/admin",
3211
- "tinycloud.sql/*",
3212
- "tinycloud.duckdb/read",
3213
- "tinycloud.duckdb/write",
3214
- "tinycloud.duckdb/admin",
3215
- "tinycloud.duckdb/describe",
3216
- "tinycloud.duckdb/export",
3217
- "tinycloud.duckdb/import",
3218
- "tinycloud.duckdb/*"
3219
- ],
3305
+ actions: [...ROOT_DELEGATION_ACTIONS],
3220
3306
  expiry: this.getSessionExpiry(),
3221
3307
  isRevoked: false,
3222
3308
  allowSubDelegation: true
@@ -3229,24 +3315,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3229
3315
  delegateDID: tcSession.verificationMethod,
3230
3316
  spaceId,
3231
3317
  path: "",
3232
- actions: [
3233
- "tinycloud.kv/put",
3234
- "tinycloud.kv/get",
3235
- "tinycloud.kv/del",
3236
- "tinycloud.kv/list",
3237
- "tinycloud.kv/metadata",
3238
- "tinycloud.sql/read",
3239
- "tinycloud.sql/write",
3240
- "tinycloud.sql/admin",
3241
- "tinycloud.sql/*",
3242
- "tinycloud.duckdb/read",
3243
- "tinycloud.duckdb/write",
3244
- "tinycloud.duckdb/admin",
3245
- "tinycloud.duckdb/describe",
3246
- "tinycloud.duckdb/export",
3247
- "tinycloud.duckdb/import",
3248
- "tinycloud.duckdb/*"
3249
- ],
3318
+ actions: [...ROOT_DELEGATION_ACTIONS],
3250
3319
  expiry: this.getSessionExpiry(),
3251
3320
  isRevoked: false,
3252
3321
  allowSubDelegation: true
@@ -3800,7 +3869,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3800
3869
  getRuntimePermissionDelegations(permissions) {
3801
3870
  this.pruneExpiredRuntimePermissionGrants();
3802
3871
  if (permissions === void 0) {
3803
- return this.runtimePermissionGrants.map((grant) => grant.delegation);
3872
+ return this.runtimePermissionGrants.filter((grant) => grant.provenance !== "primary").map((grant) => grant.delegation);
3804
3873
  }
3805
3874
  const session = this.currentTinyCloudSession();
3806
3875
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
@@ -3952,7 +4021,8 @@ var _TinyCloudNode = class _TinyCloudNode {
3952
4021
  },
3953
4022
  delegation,
3954
4023
  operations: this.permissionOperations(delegatedEntries, spaceId),
3955
- expiresAt
4024
+ expiresAt,
4025
+ provenance: "runtime"
3956
4026
  });
3957
4027
  delegations.push(delegation);
3958
4028
  }
@@ -4090,13 +4160,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4090
4160
  throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
4091
4161
  }
4092
4162
  await this.auth.hostPublicSpace(publicSpaceId);
4093
- const kvActions = [
4094
- "tinycloud.kv/put",
4095
- "tinycloud.kv/get",
4096
- "tinycloud.kv/del",
4097
- "tinycloud.kv/list",
4098
- "tinycloud.kv/metadata"
4099
- ];
4163
+ const kvActions = [import_sdk_core8.KV.PUT, import_sdk_core8.KV.GET, import_sdk_core8.KV.DEL, import_sdk_core8.KV.LIST, import_sdk_core8.KV.METADATA];
4100
4164
  const abilities = { kv: { "": kvActions } };
4101
4165
  const now = /* @__PURE__ */ new Date();
4102
4166
  const expiryMs = import_sdk_core8.EXPIRY.EPHEMERAL_MS;
@@ -4619,7 +4683,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4619
4683
  return grants;
4620
4684
  }
4621
4685
  for (const operation of operations) {
4622
- const grant = this.findGrantForOperation(operation);
4686
+ const grant = this.findGrantForOperation(operation, { excludePrimary: true });
4623
4687
  if (!grant) {
4624
4688
  return [];
4625
4689
  }
@@ -4659,7 +4723,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4659
4723
  },
4660
4724
  delegation,
4661
4725
  operations,
4662
- expiresAt: delegation.expiry
4726
+ expiresAt: delegation.expiry,
4727
+ provenance: "delegated"
4663
4728
  };
4664
4729
  }
4665
4730
  installRuntimeGrantFromServiceSession(delegation, session, expiresAt) {
@@ -4674,7 +4739,8 @@ var _TinyCloudNode = class _TinyCloudNode {
4674
4739
  session,
4675
4740
  delegation,
4676
4741
  operations,
4677
- expiresAt
4742
+ expiresAt,
4743
+ provenance: "delegated"
4678
4744
  });
4679
4745
  }
4680
4746
  delegatedResourcesForEntries(entries, spaceId) {
@@ -4774,21 +4840,28 @@ var _TinyCloudNode = class _TinyCloudNode {
4774
4840
  });
4775
4841
  return grant?.session ?? fallback;
4776
4842
  }
4777
- findGrantForOperations(operations) {
4843
+ findGrantForOperations(operations, options) {
4778
4844
  if (operations.length === 0) {
4779
4845
  return void 0;
4780
4846
  }
4781
4847
  this.pruneExpiredRuntimePermissionGrants();
4782
- return this.runtimePermissionGrants.find((grant) => {
4848
+ const covering = this.runtimePermissionGrants.filter((grant) => {
4849
+ if (options?.excludePrimary && grant.provenance === "primary") {
4850
+ return false;
4851
+ }
4783
4852
  return operations.every(
4784
4853
  (operation) => grant.operations.some(
4785
4854
  (granted) => this.operationCovers(granted, operation)
4786
4855
  )
4787
4856
  );
4788
4857
  });
4858
+ if (covering.length === 0) {
4859
+ return void 0;
4860
+ }
4861
+ return covering.find((grant) => grant.provenance === "primary") ?? covering[0];
4789
4862
  }
4790
- findGrantForOperation(operation) {
4791
- return this.findGrantForOperations([operation]);
4863
+ findGrantForOperation(operation, options) {
4864
+ return this.findGrantForOperations([operation], options);
4792
4865
  }
4793
4866
  pruneExpiredRuntimePermissionGrants() {
4794
4867
  const now = Date.now();
@@ -5025,7 +5098,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5025
5098
  session.chainId
5026
5099
  );
5027
5100
  const publicAbilities = {
5028
- kv: { "": ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/metadata"] }
5101
+ kv: { "": [import_sdk_core8.KV.GET, import_sdk_core8.KV.PUT, import_sdk_core8.KV.METADATA] }
5029
5102
  };
5030
5103
  const publicPrepared = this.wasmBindings.prepareSession({
5031
5104
  abilities: publicAbilities,
@@ -5053,7 +5126,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5053
5126
  delegationHeader: publicSession.delegationHeader,
5054
5127
  spaceId: publicSpaceId,
5055
5128
  path: "",
5056
- actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/metadata"],
5129
+ actions: [import_sdk_core8.KV.GET, import_sdk_core8.KV.PUT, import_sdk_core8.KV.METADATA],
5057
5130
  disableSubDelegation: params.disableSubDelegation ?? false,
5058
5131
  expiry: expirationTime,
5059
5132
  delegateDID: params.delegateDID,