@tinycloud/node-sdk 3.1.0-beta.1 → 3.1.0-beta.10

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
@@ -262,6 +262,10 @@ var defaultSignStrategy = { type: "auto-sign" };
262
262
  // src/authorization/NodeUserAuthorization.ts
263
263
  var DECRYPT_ACTION = ENCRYPTION.DECRYPT;
264
264
  var NETWORK_CREATE_ACTION = ENCRYPTION.NETWORK_CREATE;
265
+ function describeHostFailure(result) {
266
+ const text = result.error?.trim();
267
+ return text ? `${result.status} - ${text}` : `${result.status}`;
268
+ }
265
269
  function canonicalizeOpenKeyManifestJson(value) {
266
270
  const json = JSON.stringify(value);
267
271
  if (json === void 0) {
@@ -790,10 +794,12 @@ var NodeUserAuthorization = class {
790
794
  return;
791
795
  }
792
796
  try {
793
- const created = (await this.hostSpace()).success;
794
- if (!created) {
795
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
796
- handler.onSpaceCreationFailed?.(creationContext, err);
797
+ const created = await this.hostSpace();
798
+ if (!created.success) {
799
+ const err = Object.assign(
800
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
801
+ { cause: created }
802
+ );
797
803
  throw err;
798
804
  }
799
805
  } catch (error) {
@@ -809,8 +815,9 @@ var NodeUserAuthorization = class {
809
815
  this._tinyCloudSession.delegationHeader
810
816
  );
811
817
  if (!retryResult.success) {
812
- const err = new Error(
813
- `Failed to activate session after creating space: ${retryResult.error}`
818
+ const err = Object.assign(
819
+ new Error(`Failed to activate session after creating space: ${describeHostFailure(retryResult)}`),
820
+ { cause: retryResult }
814
821
  );
815
822
  handler.onSpaceCreationFailed?.(creationContext, err);
816
823
  throw err;
@@ -827,10 +834,12 @@ var NodeUserAuthorization = class {
827
834
  return;
828
835
  }
829
836
  try {
830
- const created = (await this.hostSpace()).success;
831
- if (!created) {
832
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
833
- handler.onSpaceCreationFailed?.(creationContext, err);
837
+ const created = await this.hostSpace();
838
+ if (!created.success) {
839
+ const err = Object.assign(
840
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
841
+ { cause: created }
842
+ );
834
843
  throw err;
835
844
  }
836
845
  } catch (error) {
@@ -846,8 +855,9 @@ var NodeUserAuthorization = class {
846
855
  this._tinyCloudSession.delegationHeader
847
856
  );
848
857
  if (!retryResult.success) {
849
- const err = new Error(
850
- `Failed to activate session after creating space: ${retryResult.error}`
858
+ const err = Object.assign(
859
+ new Error(`Failed to activate session after creating space: ${describeHostFailure(retryResult)}`),
860
+ { cause: retryResult }
851
861
  );
852
862
  handler.onSpaceCreationFailed?.(creationContext, err);
853
863
  throw err;
@@ -855,7 +865,10 @@ var NodeUserAuthorization = class {
855
865
  handler.onSpaceCreated?.(creationContext);
856
866
  return;
857
867
  }
858
- throw new Error(`Failed to activate session: ${result.error}`);
868
+ throw Object.assign(
869
+ new Error(`Failed to activate session: ${describeHostFailure(result)}`),
870
+ { cause: result }
871
+ );
859
872
  }
860
873
  recordActivationSkippedSpaces(result, primarySpaceId) {
861
874
  if (result.success) {
@@ -1645,14 +1658,14 @@ var NodeUserAuthorization = class {
1645
1658
  * Request signature via event emitter with timeout.
1646
1659
  */
1647
1660
  requestSignatureViaEmitter(request, emitter, timeout) {
1648
- return new Promise((resolve, reject) => {
1661
+ return new Promise((resolve, reject2) => {
1649
1662
  const timeoutId = setTimeout(() => {
1650
- reject(new Error("Sign request timed out"));
1663
+ reject2(new Error("Sign request timed out"));
1651
1664
  }, timeout);
1652
1665
  const respond = async (response) => {
1653
1666
  clearTimeout(timeoutId);
1654
1667
  if (!response.approved) {
1655
- reject(
1668
+ reject2(
1656
1669
  new Error(response.reason ?? "Sign request rejected via emitter")
1657
1670
  );
1658
1671
  } else {
@@ -1670,6 +1683,7 @@ import {
1670
1683
  TinyCloud,
1671
1684
  activateSessionWithHost as activateSessionWithHost2,
1672
1685
  authorizationVerdictOf,
1686
+ httpResponseError,
1673
1687
  KVService as KVService2,
1674
1688
  SQLService as SQLService2,
1675
1689
  DuckDbService as DuckDbService2,
@@ -1703,6 +1717,7 @@ import {
1703
1717
  recapCaveatsEqual,
1704
1718
  expandPermissionEntries as expandPermissionEntriesCore,
1705
1719
  isCapabilitySubset,
1720
+ isExplicitOnlyAction,
1706
1721
  parseRecapCapabilities,
1707
1722
  SERVICE_LONG_TO_SHORT,
1708
1723
  SERVICE_SHORT_TO_LONG,
@@ -1725,7 +1740,10 @@ import {
1725
1740
  CREDENTIAL_INVITATION_REQUEST_DOMAIN,
1726
1741
  validateShareDeliveryAuthorizationV3Bytes,
1727
1742
  verifyEip191MessageSignature,
1728
- signCompactUcanRootAuthorization
1743
+ signCompactUcanRootAuthorization,
1744
+ isStorageFullError,
1745
+ STORAGE_FULL_MESSAGE,
1746
+ STORAGE_WRITE_TOO_LARGE_MESSAGE
1729
1747
  } from "@tinycloud/sdk-core";
1730
1748
  import {
1731
1749
  parsePermissionHint
@@ -2313,6 +2331,25 @@ function jsonFromVerifier(value) {
2313
2331
  function verifierRecapEntries(entries) {
2314
2332
  return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
2315
2333
  }
2334
+ function exactVerifiedRecap(verified) {
2335
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
2336
+ if (!Array.isArray(exactRecap) || !exactRecap.every(
2337
+ (entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
2338
+ (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
2339
+ )
2340
+ )) {
2341
+ throw new UnsupportedSessionRestoreError(
2342
+ "it cannot reconstruct caveat-preserving persisted ReCap authority"
2343
+ );
2344
+ }
2345
+ return exactRecap.map((entry) => ({
2346
+ service: entry.service,
2347
+ space: entry.space,
2348
+ path: entry.path,
2349
+ actions: [...entry.actions],
2350
+ caveats: cloneRecapCaveats(entry.caveats)
2351
+ }));
2352
+ }
2316
2353
  var ServiceGraphLifetime = class {
2317
2354
  constructor(invokeFn, invokeAnyFn, fetchFn) {
2318
2355
  this.invokeFn = invokeFn;
@@ -2408,7 +2445,7 @@ function persistedExpiry(value) {
2408
2445
  function sameInstant(left, right) {
2409
2446
  return left.getTime() === right.getTime();
2410
2447
  }
2411
- function describeHostFailure(result) {
2448
+ function describeHostFailure2(result) {
2412
2449
  const text = result.error?.trim();
2413
2450
  return text ? `${result.status} - ${text}` : `${result.status}`;
2414
2451
  }
@@ -3215,7 +3252,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3215
3252
  marker
3216
3253
  );
3217
3254
  if (!written.ok) {
3218
- throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
3255
+ throw Object.assign(
3256
+ new Error(`Failed to write bootstrap completion marker: ${written.error.message}`),
3257
+ { cause: written.error }
3258
+ );
3219
3259
  }
3220
3260
  }
3221
3261
  isAcceptedBootstrapCompletionMarker(value) {
@@ -3261,17 +3301,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3261
3301
  rawAbilities
3262
3302
  });
3263
3303
  } catch (err) {
3264
- throw new Error(
3265
- `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3304
+ throw Object.assign(
3305
+ new Error(
3306
+ `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3307
+ ),
3308
+ { cause: err }
3266
3309
  );
3267
3310
  }
3268
3311
  sessions.set(step.space, session);
3269
3312
  }
3270
3313
  for (const step of steps) {
3271
3314
  if (step.kind !== "host") continue;
3272
- const hosted = await auth.hostOwnedSpace(step.spaceId, "bootstrap-host");
3273
- if (!hosted) {
3274
- throw new Error(`Failed to host bootstrap space: ${step.spaceId}`);
3315
+ const hosted = await auth.hostOwnedSpaceResult(step.spaceId, "bootstrap-host");
3316
+ if (!hosted.success) {
3317
+ throw Object.assign(
3318
+ new Error(`Failed to host bootstrap space ${step.spaceId}: ${describeHostFailure2(hosted)}`),
3319
+ { cause: hosted }
3320
+ );
3275
3321
  }
3276
3322
  }
3277
3323
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -3283,8 +3329,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3283
3329
  }
3284
3330
  const activated = await activateSessionWithHost2(host, session.delegationHeader);
3285
3331
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
3286
- throw new Error(
3287
- `Failed to activate bootstrap session for ${step.spaceId}: ${activated.error ?? "space was skipped"}`
3332
+ throw Object.assign(
3333
+ new Error(
3334
+ `Failed to activate bootstrap session for ${step.spaceId}: ${activated.success ? "space was skipped" : describeHostFailure2(activated)}`
3335
+ ),
3336
+ { cause: activated }
3288
3337
  );
3289
3338
  }
3290
3339
  this.registerBootstrapRuntimeGrant(
@@ -3297,7 +3346,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3297
3346
  if (step.kind === "account-index-schema") {
3298
3347
  const ensured = await this.account.index.ensure();
3299
3348
  if (!ensured.ok) {
3300
- throw new Error(`Failed to create account index schema: ${ensured.error.message}`);
3349
+ throw Object.assign(new Error(`Failed to create account index schema: ${ensured.error.message}`), { cause: ensured.error });
3301
3350
  }
3302
3351
  }
3303
3352
  if (step.kind === "seed-spaces") {
@@ -3312,8 +3361,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3312
3361
  }))
3313
3362
  );
3314
3363
  if (!registered.ok) {
3315
- throw new Error(
3316
- `Failed to seed account spaces: ${registered.error.message}`
3364
+ throw Object.assign(
3365
+ new Error(`Failed to seed account spaces: ${registered.error.message}`),
3366
+ { cause: registered.error }
3317
3367
  );
3318
3368
  }
3319
3369
  const batchError = registered.data.recoveredFromBatchError;
@@ -3334,7 +3384,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3334
3384
  { assumeUnregistered: true }
3335
3385
  );
3336
3386
  if (!registered.ok) {
3337
- throw new Error(`Failed to seed bootstrap applications: ${registered.error.message}`);
3387
+ throw Object.assign(new Error(`Failed to seed bootstrap applications: ${registered.error.message}`), { cause: registered.error });
3338
3388
  }
3339
3389
  }
3340
3390
  if (step.kind === "encryption-network-create") {
@@ -3352,8 +3402,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3352
3402
  ]
3353
3403
  });
3354
3404
  if (!migrated.ok) {
3355
- throw new Error(
3356
- `Failed to create secret_records schema: ${migrated.error.message}`
3405
+ throw Object.assign(
3406
+ new Error(`Failed to create secret_records schema: ${migrated.error.message}`),
3407
+ { cause: migrated.error }
3357
3408
  );
3358
3409
  }
3359
3410
  }
@@ -3620,6 +3671,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3620
3671
  );
3621
3672
  return;
3622
3673
  }
3674
+ if (isStorageFullError(error) || isStorageFullError(error?.cause) || message.includes(STORAGE_FULL_MESSAGE) || message.includes(STORAGE_WRITE_TOO_LARGE_MESSAGE)) {
3675
+ console.warn("TinyCloud account registry sync stopped: storage is full", error);
3676
+ return;
3677
+ }
3623
3678
  lastError = error;
3624
3679
  if (attempt < delays.length - 1) {
3625
3680
  await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
@@ -3694,14 +3749,14 @@ var _TinyCloudNode = class _TinyCloudNode {
3694
3749
  }
3695
3750
  if (!activation.success && activation.status !== 404) {
3696
3751
  throw Object.assign(
3697
- new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure(activation)}`),
3752
+ new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure2(activation)}`),
3698
3753
  { cause: activation }
3699
3754
  );
3700
3755
  }
3701
3756
  const created = await this.auth.hostOwnedSpaceResult(spaceId);
3702
3757
  if (!created.success) {
3703
3758
  throw Object.assign(
3704
- new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure(created)}`),
3759
+ new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
3705
3760
  { cause: created }
3706
3761
  );
3707
3762
  }
@@ -3710,7 +3765,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3710
3765
  if (!retry.success || retry.skipped?.includes(spaceId)) {
3711
3766
  throw Object.assign(
3712
3767
  new Error(
3713
- `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure(retry)}`
3768
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure2(retry)}`
3714
3769
  ),
3715
3770
  { cause: retry }
3716
3771
  );
@@ -3747,19 +3802,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3747
3802
  if (!host) {
3748
3803
  throw new Error("Owned space hosting requires a TinyCloud host");
3749
3804
  }
3750
- const hosted = await this.auth.hostOwnedSpace(
3751
- spaceId
3752
- );
3753
- if (!hosted) {
3754
- throw new Error(`Failed to host owned space: ${spaceId}`);
3805
+ const hosted = await this.auth.hostOwnedSpaceResult(spaceId);
3806
+ if (!hosted.success) {
3807
+ throw Object.assign(
3808
+ new Error(`Failed to host owned space ${spaceId}: ${describeHostFailure2(hosted)}`),
3809
+ { cause: hosted }
3810
+ );
3755
3811
  }
3756
3812
  const activation = await activateSessionWithHost2(
3757
3813
  host,
3758
3814
  session.delegationHeader
3759
3815
  );
3760
3816
  if (!activation.success || activation.skipped?.includes(spaceId)) {
3761
- throw new Error(
3762
- `Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
3817
+ throw Object.assign(
3818
+ new Error(
3819
+ `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
3820
+ ),
3821
+ { cause: activation }
3763
3822
  );
3764
3823
  }
3765
3824
  try {
@@ -3955,23 +4014,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3955
4014
  throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3956
4015
  }
3957
4016
  })();
3958
- const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
3959
- if (!Array.isArray(exactRecap) || !exactRecap.every(
3960
- (entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
3961
- (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
3962
- )
3963
- )) {
3964
- throw new UnsupportedSessionRestoreError(
3965
- "it cannot reconstruct caveat-preserving persisted ReCap authority"
3966
- );
3967
- }
3968
- stagedRecap = exactRecap.map((entry) => ({
3969
- service: entry.service,
3970
- space: entry.space,
3971
- path: entry.path,
3972
- actions: [...entry.actions],
3973
- caveats: cloneRecapCaveats(entry.caveats)
3974
- }));
4017
+ stagedRecap = exactVerifiedRecap(verified);
3975
4018
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3976
4019
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3977
4020
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
@@ -4775,7 +4818,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4775
4818
  if (cached) return cached;
4776
4819
  const response = await fetch(`${this.config.host}/info`);
4777
4820
  if (!response.ok) {
4778
- throw new Error(`Failed to fetch node info: HTTP ${response.status}`);
4821
+ throw await httpResponseError(response, "Failed to fetch node info");
4779
4822
  }
4780
4823
  const info = await response.json();
4781
4824
  if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
@@ -4825,9 +4868,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4825
4868
  );
4826
4869
  if (response.status === 404) return null;
4827
4870
  if (!response.ok) {
4828
- throw new Error(
4829
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
4830
- );
4871
+ throw await httpResponseError(response, `Failed to fetch encryption network ${networkId}`);
4831
4872
  }
4832
4873
  const body = await response.json();
4833
4874
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
@@ -5328,7 +5369,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5328
5369
  headers: { accept: "application/json", "content-type": "application/json", authorization },
5329
5370
  body: canonicalizeEncryptionJson(request)
5330
5371
  });
5331
- if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
5372
+ if (!response.ok) throw await httpResponseError(response, "V3 share delivery authorization failed");
5332
5373
  const verified = validateShareDeliveryAuthorizationV3Bytes(new Uint8Array(await response.arrayBuffer()), {
5333
5374
  request,
5334
5375
  senderKeyDid: this.credentialHolderDid,
@@ -5550,9 +5591,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5550
5591
  );
5551
5592
  }
5552
5593
  if (!response.ok) {
5553
- throw new Error(
5554
- `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
5555
- );
5594
+ throw await httpResponseError(response, `Failed to create encryption network ${networkId}`);
5556
5595
  }
5557
5596
  const created = await response.json();
5558
5597
  return created.descriptor;
@@ -5836,6 +5875,42 @@ var _TinyCloudNode = class _TinyCloudNode {
5836
5875
  (left, right) => left.service.localeCompare(right.service) || (left.space ?? "").localeCompare(right.space ?? "") || left.path.localeCompare(right.path) || left.actions.join("\0").localeCompare(right.actions.join("\0"))
5837
5876
  );
5838
5877
  }
5878
+ /**
5879
+ * Verify a wallet-signed session grant (a CACAO built from a SIWE and its
5880
+ * EIP-191 signature) issued to this instance's live session key. The
5881
+ * verifier rebuilds the CACAO from `siwe` and `signature`, so the grant is
5882
+ * accepted only when its authorization bytes and CID are exactly that CACAO,
5883
+ * its signer is `address`, its audience is this session key, it is valid
5884
+ * now, and its ReCap covers `spaceId` or names only raw encryption networks
5885
+ * (a decrypt-only grant has no space). Returns the signed ReCap and expiry.
5886
+ * Bindings without `validateSessionGrant` fall back to the session verifier,
5887
+ * which also refuses decrypt-only grants.
5888
+ */
5889
+ verifySessionGrant(grant) {
5890
+ const bindings = this.wasmBindings;
5891
+ const validate = typeof bindings.validateSessionGrant === "function" ? bindings.validateSessionGrant.bind(bindings) : bindings.validatePersistedSession?.bind(bindings);
5892
+ if (validate === void 0) {
5893
+ throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
5894
+ }
5895
+ const verified = validate({
5896
+ delegationHeader: { Authorization: grant.delegationHeader.Authorization },
5897
+ delegationCid: grant.delegationCid,
5898
+ spaceId: grant.spaceId,
5899
+ jwk: this.sessionKeyJwk,
5900
+ address: canonicalizeAddress2(grant.address),
5901
+ chainId: grant.chainId,
5902
+ siwe: grant.siwe,
5903
+ signature: grant.signature
5904
+ });
5905
+ if (typeof verified.expiresAt !== "string") {
5906
+ throw new Error("Session grant has no signed expiry.");
5907
+ }
5908
+ const expiresAt = new Date(verified.expiresAt);
5909
+ if (!Number.isFinite(expiresAt.getTime())) {
5910
+ throw new Error("Session grant has an invalid signed expiry.");
5911
+ }
5912
+ return { recap: exactVerifiedRecap(verified), expiresAt };
5913
+ }
5839
5914
  /**
5840
5915
  * Install a portable runtime permission delegation into this SDK instance so
5841
5916
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -5860,8 +5935,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5860
5935
  delegation.delegationHeader
5861
5936
  );
5862
5937
  if (!activateResult.success) {
5863
- throw new Error(
5864
- `Failed to activate runtime permission delegation: ${activateResult.error}`
5938
+ throw Object.assign(
5939
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
5940
+ { cause: activateResult }
5865
5941
  );
5866
5942
  }
5867
5943
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
@@ -5957,8 +6033,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5957
6033
  delegatedSession.delegationHeader
5958
6034
  );
5959
6035
  if (!activateResult.success) {
5960
- throw new Error(
5961
- `Failed to activate runtime permission delegation: ${activateResult.error}`
6036
+ throw Object.assign(
6037
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
6038
+ { cause: activateResult }
5962
6039
  );
5963
6040
  }
5964
6041
  const delegation = this.runtimeDelegationFromSession(
@@ -5966,7 +6043,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5966
6043
  delegatedEntries,
5967
6044
  spaceId,
5968
6045
  session,
5969
- expiresAt
6046
+ expiresAt,
6047
+ { siwe: prepared.siwe, signature }
5970
6048
  );
5971
6049
  this.runtimePermissionGrants.push({
5972
6050
  session: {
@@ -6143,7 +6221,10 @@ var _TinyCloudNode = class _TinyCloudNode {
6143
6221
  delegationSession.delegationHeader
6144
6222
  );
6145
6223
  if (!activateResult.success) {
6146
- throw new Error(`Failed to activate public space delegation: ${activateResult.error}`);
6224
+ throw Object.assign(
6225
+ new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
6226
+ { cause: activateResult }
6227
+ );
6147
6228
  }
6148
6229
  if (this._capabilityRegistry && this.session) {
6149
6230
  const sessionKey = {
@@ -6496,8 +6577,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6496
6577
  delegationHeader
6497
6578
  );
6498
6579
  if (!activateResult.success) {
6499
- throw new Error(
6500
- `Failed to activate delegation with host: ${activateResult.error}`
6580
+ throw Object.assign(
6581
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6582
+ { cause: activateResult }
6501
6583
  );
6502
6584
  }
6503
6585
  return {
@@ -6533,8 +6615,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6533
6615
  delegationHeader
6534
6616
  );
6535
6617
  if (!activateResult.success) {
6536
- throw new Error(
6537
- `Failed to activate delegation with host: ${activateResult.error}`
6618
+ throw Object.assign(
6619
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6620
+ { cause: activateResult }
6538
6621
  );
6539
6622
  }
6540
6623
  return {
@@ -6747,7 +6830,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6747
6830
  }
6748
6831
  return grants;
6749
6832
  }
6750
- runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
6833
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt, siweProof) {
6751
6834
  const resources = this.delegatedResourcesForEntries(entries, spaceId);
6752
6835
  const primary = resources[0];
6753
6836
  return {
@@ -6762,7 +6845,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6762
6845
  delegateDID: session.verificationMethod,
6763
6846
  ownerAddress: session.address,
6764
6847
  chainId: session.chainId,
6765
- host: this.config.host
6848
+ host: this.config.host,
6849
+ siweProof
6766
6850
  };
6767
6851
  }
6768
6852
  runtimeGrantFromDelegation(delegation, session) {
@@ -6955,6 +7039,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6955
7039
  if (grantedAction === requestedAction) {
6956
7040
  return true;
6957
7041
  }
7042
+ if (isExplicitOnlyAction(requestedAction)) {
7043
+ return false;
7044
+ }
6958
7045
  if (grantedAction.endsWith("/*")) {
6959
7046
  const prefix = grantedAction.slice(0, -2);
6960
7047
  return requestedAction.startsWith(`${prefix}/`);
@@ -7141,7 +7228,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7141
7228
  delegationSession.delegationHeader
7142
7229
  );
7143
7230
  if (!activateResult.success) {
7144
- throw new Error(`Failed to activate delegation: ${activateResult.error}`);
7231
+ throw Object.assign(
7232
+ new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
7233
+ { cause: activateResult }
7234
+ );
7145
7235
  }
7146
7236
  const result = {
7147
7237
  cid: delegationSession.delegationCid,
@@ -7291,7 +7381,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7291
7381
  invokerSession.delegationHeader
7292
7382
  );
7293
7383
  if (!activateResult.success) {
7294
- throw new Error(`Failed to activate delegated session: ${activateResult.error}`);
7384
+ throw Object.assign(
7385
+ new Error(`Failed to activate delegated session: ${describeHostFailure2(activateResult)}`),
7386
+ { cause: activateResult }
7387
+ );
7295
7388
  }
7296
7389
  const session = {
7297
7390
  address: mySession.address,
@@ -7408,7 +7501,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7408
7501
  subDelegationSession.delegationHeader
7409
7502
  );
7410
7503
  if (!activateResult.success) {
7411
- throw new Error(`Failed to activate sub-delegation: ${activateResult.error}`);
7504
+ throw Object.assign(
7505
+ new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
7506
+ { cause: activateResult }
7507
+ );
7412
7508
  }
7413
7509
  return {
7414
7510
  cid: subDelegationSession.delegationCid,
@@ -7473,6 +7569,16 @@ import {
7473
7569
  decodeBase64Url,
7474
7570
  principalDidEquals as principalDidEquals3
7475
7571
  } from "@tinycloud/sdk-core";
7572
+ var RuntimeDelegationRejectedError = class extends Error {
7573
+ constructor(reason, message) {
7574
+ super(message);
7575
+ this.name = "RuntimeDelegationRejectedError";
7576
+ this.reason = reason;
7577
+ }
7578
+ };
7579
+ function reject(reason, message) {
7580
+ throw new RuntimeDelegationRejectedError(reason, message);
7581
+ }
7476
7582
  var SERVICE_BY_SHORT_NAME = {
7477
7583
  kv: "tinycloud.kv",
7478
7584
  sql: "tinycloud.sql",
@@ -7619,6 +7725,15 @@ function signedAuthorityFromCompactUcan(authorization) {
7619
7725
  abilities
7620
7726
  ));
7621
7727
  }
7728
+ return canonicalSignedAuthority({
7729
+ issuer: payload.iss,
7730
+ audience: payload.aud,
7731
+ expiry,
7732
+ proofs: [...payload.prf],
7733
+ ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth }
7734
+ }, permissions);
7735
+ }
7736
+ function canonicalSignedAuthority(signed, permissions) {
7622
7737
  if (permissions.length === 0) {
7623
7738
  throw new Error("Validated runtime delegation has no signed capabilities.");
7624
7739
  }
@@ -7630,15 +7745,84 @@ function signedAuthorityFromCompactUcan(authorization) {
7630
7745
  actions: [...permission.actions],
7631
7746
  ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
7632
7747
  }));
7633
- return {
7634
- issuer: payload.iss,
7635
- audience: payload.aud,
7636
- expiry,
7637
- proofs: [...payload.prf],
7638
- ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
7639
- permissions: canonicalPermissions,
7640
- resources: canonicalResources
7641
- };
7748
+ return { ...signed, permissions: canonicalPermissions, resources: canonicalResources };
7749
+ }
7750
+ var RAW_ENCRYPTION_PREFIX = "urn:tinycloud:encryption:";
7751
+ var PKH_SPACE = /^tinycloud:pkh:eip155:(\d+):(0x[0-9a-fA-F]{40}):(.+)$/;
7752
+ function comparableSpace(space) {
7753
+ const match = PKH_SPACE.exec(space);
7754
+ return match ? `tinycloud:pkh:eip155:${match[1]}:${match[2].toLowerCase()}:${match[3]}` : space;
7755
+ }
7756
+ function signedAuthorityFromSessionGrant(node, delegation, authorization, cid) {
7757
+ const proof = delegation.siweProof;
7758
+ if (proof === void 0 || proof === null || typeof proof !== "object" || typeof proof.siwe !== "string" || typeof proof.signature !== "string") {
7759
+ reject(
7760
+ "proof_missing",
7761
+ "Runtime delegation is a wallet-signed session grant stored without its signed SIWE proof, so its authority cannot be verified."
7762
+ );
7763
+ }
7764
+ if (typeof node.verifySessionGrant !== "function") {
7765
+ reject("unsupported", "This runtime cannot verify wallet-signed session grants.");
7766
+ }
7767
+ const owner = `did:pkh:eip155:${delegation.chainId}:${delegation.ownerAddress}`;
7768
+ if (typeof node.did !== "string" || !didPrincipalsMatch(owner, node.did)) {
7769
+ reject("owner_mismatch", "Runtime delegation is not signed by this session's owner.");
7770
+ }
7771
+ let verified;
7772
+ try {
7773
+ verified = node.verifySessionGrant({
7774
+ delegationHeader: { Authorization: authorization },
7775
+ delegationCid: cid,
7776
+ spaceId: delegation.spaceId,
7777
+ address: delegation.ownerAddress,
7778
+ chainId: delegation.chainId,
7779
+ siwe: proof.siwe,
7780
+ signature: proof.signature
7781
+ });
7782
+ } catch (error) {
7783
+ reject("proof_invalid", error instanceof Error ? error.message : String(error));
7784
+ }
7785
+ const space = comparableSpace(delegation.spaceId);
7786
+ const spaceOwner = PKH_SPACE.exec(delegation.spaceId);
7787
+ if (!spaceOwner || !didPrincipalsMatch(`did:pkh:eip155:${spaceOwner[1]}:${spaceOwner[2]}`, owner)) {
7788
+ reject("authority_invalid", "Runtime delegation space is not owned by its signer.");
7789
+ }
7790
+ const groups = /* @__PURE__ */ new Map();
7791
+ for (const entry of verified.recap) {
7792
+ const service = entry.service.startsWith("tinycloud.") ? entry.service : `tinycloud.${entry.service}`;
7793
+ const raw = entry.space === "encryption";
7794
+ if (raw) {
7795
+ if (service !== "tinycloud.encryption" || !entry.path.startsWith(RAW_ENCRYPTION_PREFIX)) {
7796
+ reject("authority_invalid", "Runtime delegation has an unsupported signed raw resource.");
7797
+ }
7798
+ const networkOwner = entry.path.slice(RAW_ENCRYPTION_PREFIX.length, entry.path.lastIndexOf(":"));
7799
+ if (!didPrincipalsMatch(networkOwner, owner)) {
7800
+ reject("authority_invalid", "Runtime delegation names an encryption network its signer does not own.");
7801
+ }
7802
+ } else if (service === "tinycloud.encryption" || comparableSpace(entry.space) !== space) {
7803
+ reject("authority_invalid", "Runtime delegation has signed authority outside its space.");
7804
+ }
7805
+ const caveats = signedCaveats(entry.caveats ?? []);
7806
+ const key = JSON.stringify([service, raw ? "" : entry.space, entry.path, canonicalizeRecapCaveats2(caveats)]);
7807
+ const group = groups.get(key) ?? {
7808
+ service,
7809
+ ...raw ? {} : { space: entry.space },
7810
+ path: entry.path,
7811
+ actions: [],
7812
+ ...caveats === void 0 ? {} : { caveats }
7813
+ };
7814
+ for (const action of entry.actions) {
7815
+ if (!group.actions.includes(action)) group.actions.push(action);
7816
+ }
7817
+ groups.set(key, group);
7818
+ }
7819
+ return canonicalSignedAuthority({
7820
+ issuer: owner,
7821
+ // The verifier bound the SIWE audience to this runtime's own session key.
7822
+ audience: node.sessionDid,
7823
+ expiry: verified.expiresAt,
7824
+ proofs: []
7825
+ }, [...groups.values()]);
7642
7826
  }
7643
7827
  function canonicalResourcesFromPortableDelegation(delegation) {
7644
7828
  if (delegation.resources !== void 0 && delegation.resources.length > 0) {
@@ -7674,46 +7858,58 @@ function resourcesMatch(actual, expected) {
7674
7858
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
7675
7859
  const host = normalizedHost(options.host);
7676
7860
  if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
7677
- throw new Error(
7861
+ reject(
7862
+ "host_mismatch",
7678
7863
  `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
7679
7864
  );
7680
7865
  }
7681
7866
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
7682
- throw new Error("Runtime delegation has an invalid expiry.");
7867
+ reject("authority_invalid", "Runtime delegation has an invalid expiry.");
7683
7868
  }
7684
7869
  if (delegation.expiry.getTime() <= Date.now()) {
7685
- throw new Error("Runtime delegation is expired.");
7870
+ reject("expired", "Runtime delegation is expired.");
7686
7871
  }
7687
7872
  const authorization = delegation.delegationHeader?.Authorization;
7688
7873
  if (typeof authorization !== "string" || authorization.length === 0) {
7689
- throw new Error("Runtime delegation is missing authorization bytes.");
7874
+ reject("authority_invalid", "Runtime delegation is missing authorization bytes.");
7690
7875
  }
7691
7876
  const cid = node.computeDelegationCid(authorization);
7692
7877
  if (cid !== delegation.cid) {
7693
- throw new Error("Runtime delegation CID does not match authorization bytes.");
7878
+ reject("cid_mismatch", "Runtime delegation CID does not match authorization bytes.");
7879
+ }
7880
+ const compact = authorizationWithoutBearer(authorization).split(".").length === 3;
7881
+ let signed;
7882
+ try {
7883
+ signed = compact ? signedAuthorityFromCompactUcan(authorization) : signedAuthorityFromSessionGrant(node, delegation, authorization, cid);
7884
+ } catch (error) {
7885
+ if (error instanceof RuntimeDelegationRejectedError) throw error;
7886
+ reject("authority_invalid", error instanceof Error ? error.message : String(error));
7694
7887
  }
7695
- const signed = signedAuthorityFromCompactUcan(authorization);
7696
7888
  if (signed.expiry.getTime() <= Date.now()) {
7697
- throw new Error("Runtime delegation is expired.");
7889
+ reject("expired", "Runtime delegation is expired.");
7698
7890
  }
7699
7891
  if (delegation.expiry.getTime() !== signed.expiry.getTime()) {
7700
- throw new Error("Runtime delegation expiry does not match signed authority.");
7892
+ reject("authority_invalid", "Runtime delegation expiry does not match signed authority.");
7701
7893
  }
7702
7894
  if (!didPrincipalsMatch(signed.audience, node.sessionDid)) {
7703
- throw new Error(
7895
+ reject(
7896
+ "audience_mismatch",
7704
7897
  `Runtime delegation targets ${signed.audience} but this session is ${node.sessionDid}.`
7705
7898
  );
7706
7899
  }
7707
7900
  if (!didPrincipalsMatch(delegation.delegateDID, signed.audience)) {
7708
- throw new Error("Runtime delegation audience does not match signed authority.");
7901
+ reject("audience_mismatch", "Runtime delegation audience does not match signed authority.");
7902
+ }
7903
+ if (compact && !resourcesMatch(canonicalResourcesFromPortableDelegation(delegation), signed.resources)) {
7904
+ reject("resources_mismatch", "Runtime delegation resources do not match signed authority.");
7709
7905
  }
7710
- const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
7711
- if (!resourcesMatch(declaredResources, signed.resources)) {
7712
- throw new Error("Runtime delegation resources do not match signed authority.");
7906
+ if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
7907
+ throw new Error("Runtime delegation is not authorized for this runtime.");
7713
7908
  }
7714
7909
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7715
7910
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
7716
- throw new Error(
7911
+ reject(
7912
+ "cid_mismatch",
7717
7913
  "A different authorization is already installed for this runtime delegation CID."
7718
7914
  );
7719
7915
  }
@@ -7743,22 +7939,28 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
7743
7939
  })),
7744
7940
  expiry: new Date(signed.expiry),
7745
7941
  delegateDID: signed.audience,
7746
- host
7942
+ host,
7943
+ ...compact ? {} : { siweProof: delegation.siweProof }
7747
7944
  };
7748
- if (node.isSessionOnly === true && typeof node.did === "string" && principalDidEquals3(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7749
- await node.useDelegation(installedCandidate);
7750
- } else {
7751
- await node.useRuntimeDelegation(installedCandidate);
7945
+ try {
7946
+ if (node.isSessionOnly === true && typeof node.did === "string" && principalDidEquals3(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7947
+ await node.useDelegation(installedCandidate);
7948
+ } else {
7949
+ await node.useRuntimeDelegation(installedCandidate);
7950
+ }
7951
+ } catch (error) {
7952
+ reject("activation_rejected", error instanceof Error ? error.message : String(error));
7752
7953
  }
7753
7954
  const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7754
7955
  if (!installed) {
7755
- throw new Error("Runtime delegation activation did not install the validated authority.");
7956
+ reject("activation_rejected", "Runtime delegation activation did not install the validated authority.");
7756
7957
  }
7757
7958
  if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed.audience) || installed.expiry.getTime() !== signed.expiry.getTime() || !resourcesMatch(
7758
7959
  canonicalResourcesFromPortableDelegation(installed),
7759
7960
  signed.resources
7760
7961
  ) || normalizedHost(installed.host ?? "") !== host) {
7761
- throw new Error(
7962
+ reject(
7963
+ "activation_rejected",
7762
7964
  "Runtime delegation activation installed authority that differs from the validated authority."
7763
7965
  );
7764
7966
  }