@tinycloud/node-sdk 3.0.1-beta.0 → 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) {
@@ -726,21 +730,28 @@ var NodeUserAuthorization = class {
726
730
  });
727
731
  const signature = await this.signMessage(siwe, purpose);
728
732
  const headers = this.wasm.siweToDelegationHeaders({ siwe, signature });
729
- const result = await submitHostDelegation(host, headers);
730
- return result.success;
733
+ return submitHostDelegation(host, headers);
731
734
  }
732
735
  /**
733
736
  * Create a specific space on the server via host delegation.
734
737
  * Used for lazy creation of additional spaces (e.g., public).
735
738
  */
736
739
  async hostPublicSpace(spaceId) {
737
- return this.hostSpace(spaceId);
740
+ return (await this.hostSpace(spaceId)).success;
738
741
  }
739
742
  /**
740
743
  * Create a specific owned space on the server via host delegation.
741
744
  * Used by manifest registry setup for the account space.
742
745
  */
743
746
  async hostOwnedSpace(spaceId, purpose) {
747
+ return (await this.hostOwnedSpaceResult(spaceId, purpose)).success;
748
+ }
749
+ /**
750
+ * {@link hostOwnedSpace} with the full host-delegation result, so a failure
751
+ * keeps its HTTP status (e.g. a typed 401/403) instead of collapsing to
752
+ * `false`.
753
+ */
754
+ async hostOwnedSpaceResult(spaceId, purpose) {
744
755
  return this.hostSpace(spaceId, purpose);
745
756
  }
746
757
  /**
@@ -784,9 +795,11 @@ var NodeUserAuthorization = class {
784
795
  }
785
796
  try {
786
797
  const created = await this.hostSpace();
787
- if (!created) {
788
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
789
- handler.onSpaceCreationFailed?.(creationContext, err);
798
+ if (!created.success) {
799
+ const err = Object.assign(
800
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
801
+ { cause: created }
802
+ );
790
803
  throw err;
791
804
  }
792
805
  } catch (error) {
@@ -802,8 +815,9 @@ var NodeUserAuthorization = class {
802
815
  this._tinyCloudSession.delegationHeader
803
816
  );
804
817
  if (!retryResult.success) {
805
- const err = new Error(
806
- `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 }
807
821
  );
808
822
  handler.onSpaceCreationFailed?.(creationContext, err);
809
823
  throw err;
@@ -821,9 +835,11 @@ var NodeUserAuthorization = class {
821
835
  }
822
836
  try {
823
837
  const created = await this.hostSpace();
824
- if (!created) {
825
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
826
- handler.onSpaceCreationFailed?.(creationContext, err);
838
+ if (!created.success) {
839
+ const err = Object.assign(
840
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
841
+ { cause: created }
842
+ );
827
843
  throw err;
828
844
  }
829
845
  } catch (error) {
@@ -839,8 +855,9 @@ var NodeUserAuthorization = class {
839
855
  this._tinyCloudSession.delegationHeader
840
856
  );
841
857
  if (!retryResult.success) {
842
- const err = new Error(
843
- `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 }
844
861
  );
845
862
  handler.onSpaceCreationFailed?.(creationContext, err);
846
863
  throw err;
@@ -848,7 +865,10 @@ var NodeUserAuthorization = class {
848
865
  handler.onSpaceCreated?.(creationContext);
849
866
  return;
850
867
  }
851
- 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
+ );
852
872
  }
853
873
  recordActivationSkippedSpaces(result, primarySpaceId) {
854
874
  if (result.success) {
@@ -1638,14 +1658,14 @@ var NodeUserAuthorization = class {
1638
1658
  * Request signature via event emitter with timeout.
1639
1659
  */
1640
1660
  requestSignatureViaEmitter(request, emitter, timeout) {
1641
- return new Promise((resolve, reject) => {
1661
+ return new Promise((resolve, reject2) => {
1642
1662
  const timeoutId = setTimeout(() => {
1643
- reject(new Error("Sign request timed out"));
1663
+ reject2(new Error("Sign request timed out"));
1644
1664
  }, timeout);
1645
1665
  const respond = async (response) => {
1646
1666
  clearTimeout(timeoutId);
1647
1667
  if (!response.approved) {
1648
- reject(
1668
+ reject2(
1649
1669
  new Error(response.reason ?? "Sign request rejected via emitter")
1650
1670
  );
1651
1671
  } else {
@@ -1662,6 +1682,8 @@ var NodeUserAuthorization = class {
1662
1682
  import {
1663
1683
  TinyCloud,
1664
1684
  activateSessionWithHost as activateSessionWithHost2,
1685
+ authorizationVerdictOf,
1686
+ httpResponseError,
1665
1687
  KVService as KVService2,
1666
1688
  SQLService as SQLService2,
1667
1689
  DuckDbService as DuckDbService2,
@@ -1695,6 +1717,7 @@ import {
1695
1717
  recapCaveatsEqual,
1696
1718
  expandPermissionEntries as expandPermissionEntriesCore,
1697
1719
  isCapabilitySubset,
1720
+ isExplicitOnlyAction,
1698
1721
  parseRecapCapabilities,
1699
1722
  SERVICE_LONG_TO_SHORT,
1700
1723
  SERVICE_SHORT_TO_LONG,
@@ -1717,7 +1740,10 @@ import {
1717
1740
  CREDENTIAL_INVITATION_REQUEST_DOMAIN,
1718
1741
  validateShareDeliveryAuthorizationV3Bytes,
1719
1742
  verifyEip191MessageSignature,
1720
- signCompactUcanRootAuthorization
1743
+ signCompactUcanRootAuthorization,
1744
+ isStorageFullError,
1745
+ STORAGE_FULL_MESSAGE,
1746
+ STORAGE_WRITE_TOO_LARGE_MESSAGE
1721
1747
  } from "@tinycloud/sdk-core";
1722
1748
  import {
1723
1749
  parsePermissionHint
@@ -2305,6 +2331,25 @@ function jsonFromVerifier(value) {
2305
2331
  function verifierRecapEntries(entries) {
2306
2332
  return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
2307
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
+ }
2308
2353
  var ServiceGraphLifetime = class {
2309
2354
  constructor(invokeFn, invokeAnyFn, fetchFn) {
2310
2355
  this.invokeFn = invokeFn;
@@ -2400,6 +2445,10 @@ function persistedExpiry(value) {
2400
2445
  function sameInstant(left, right) {
2401
2446
  return left.getTime() === right.getTime();
2402
2447
  }
2448
+ function describeHostFailure2(result) {
2449
+ const text = result.error?.trim();
2450
+ return text ? `${result.status} - ${text}` : `${result.status}`;
2451
+ }
2403
2452
  function ownerDelegationPermissions(params) {
2404
2453
  const hasPermissions = Object.prototype.hasOwnProperty.call(params, "permissions");
2405
2454
  const hasLegacy = Object.prototype.hasOwnProperty.call(params, "path") || Object.prototype.hasOwnProperty.call(params, "actions");
@@ -3203,7 +3252,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3203
3252
  marker
3204
3253
  );
3205
3254
  if (!written.ok) {
3206
- 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
+ );
3207
3259
  }
3208
3260
  }
3209
3261
  isAcceptedBootstrapCompletionMarker(value) {
@@ -3249,17 +3301,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3249
3301
  rawAbilities
3250
3302
  });
3251
3303
  } catch (err) {
3252
- throw new Error(
3253
- `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 }
3254
3309
  );
3255
3310
  }
3256
3311
  sessions.set(step.space, session);
3257
3312
  }
3258
3313
  for (const step of steps) {
3259
3314
  if (step.kind !== "host") continue;
3260
- const hosted = await auth.hostOwnedSpace(step.spaceId, "bootstrap-host");
3261
- if (!hosted) {
3262
- 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
+ );
3263
3321
  }
3264
3322
  }
3265
3323
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -3271,8 +3329,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3271
3329
  }
3272
3330
  const activated = await activateSessionWithHost2(host, session.delegationHeader);
3273
3331
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
3274
- throw new Error(
3275
- `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 }
3276
3337
  );
3277
3338
  }
3278
3339
  this.registerBootstrapRuntimeGrant(
@@ -3285,7 +3346,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3285
3346
  if (step.kind === "account-index-schema") {
3286
3347
  const ensured = await this.account.index.ensure();
3287
3348
  if (!ensured.ok) {
3288
- 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 });
3289
3350
  }
3290
3351
  }
3291
3352
  if (step.kind === "seed-spaces") {
@@ -3300,8 +3361,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3300
3361
  }))
3301
3362
  );
3302
3363
  if (!registered.ok) {
3303
- throw new Error(
3304
- `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 }
3305
3367
  );
3306
3368
  }
3307
3369
  const batchError = registered.data.recoveredFromBatchError;
@@ -3322,7 +3384,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3322
3384
  { assumeUnregistered: true }
3323
3385
  );
3324
3386
  if (!registered.ok) {
3325
- 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 });
3326
3388
  }
3327
3389
  }
3328
3390
  if (step.kind === "encryption-network-create") {
@@ -3340,8 +3402,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3340
3402
  ]
3341
3403
  });
3342
3404
  if (!migrated.ok) {
3343
- throw new Error(
3344
- `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 }
3345
3408
  );
3346
3409
  }
3347
3410
  }
@@ -3513,8 +3576,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3513
3576
  await this.ensureOwnedSpaceHostedById(accountSpaceId);
3514
3577
  const result = await this.account.applications.register(request.manifests);
3515
3578
  if (!result.ok) {
3516
- throw new Error(
3517
- `Failed to write manifest registry records: ${result.error.message}`
3579
+ throw Object.assign(
3580
+ new Error(`Failed to write manifest registry records: ${result.error.message}`),
3581
+ { cause: result.error }
3518
3582
  );
3519
3583
  }
3520
3584
  }
@@ -3539,7 +3603,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3539
3603
  if (this.currentSessionCanListSpaces()) {
3540
3604
  const spaces = await this.account.spaces.syncAccessible();
3541
3605
  if (!spaces.ok) {
3542
- throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
3606
+ throw Object.assign(
3607
+ new Error(`Failed to sync account spaces: ${spaces.error.message}`),
3608
+ { cause: spaces.error }
3609
+ );
3543
3610
  }
3544
3611
  }
3545
3612
  });
@@ -3594,14 +3661,20 @@ var _TinyCloudNode = class _TinyCloudNode {
3594
3661
  await task();
3595
3662
  return;
3596
3663
  } catch (error) {
3664
+ const verdict = authorizationVerdictOf(error);
3597
3665
  const message = error instanceof Error ? error.message : String(error);
3598
- if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
3666
+ const isAuthorizationVerdict = verdict === void 0 ? /Unauthorized Action|\b(?:401|403)\b/.test(message) : verdict !== "other";
3667
+ if (isAuthorizationVerdict) {
3599
3668
  console.warn(
3600
3669
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
3601
3670
  error
3602
3671
  );
3603
3672
  return;
3604
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
+ }
3605
3678
  lastError = error;
3606
3679
  if (attempt < delays.length - 1) {
3607
3680
  await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
@@ -3675,19 +3748,26 @@ var _TinyCloudNode = class _TinyCloudNode {
3675
3748
  return;
3676
3749
  }
3677
3750
  if (!activation.success && activation.status !== 404) {
3678
- throw new Error(
3679
- `Failed to check owned space ${spaceId}: ${activation.error ?? activation.status}`
3751
+ throw Object.assign(
3752
+ new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure2(activation)}`),
3753
+ { cause: activation }
3680
3754
  );
3681
3755
  }
3682
- const created = await this.auth.hostOwnedSpace(spaceId);
3683
- if (!created) {
3684
- throw new Error(`Failed to create owned space: ${spaceId}`);
3756
+ const created = await this.auth.hostOwnedSpaceResult(spaceId);
3757
+ if (!created.success) {
3758
+ throw Object.assign(
3759
+ new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
3760
+ { cause: created }
3761
+ );
3685
3762
  }
3686
3763
  await new Promise((resolve) => setTimeout(resolve, 100));
3687
3764
  const retry = await activateSessionWithHost2(host, session.delegationHeader);
3688
3765
  if (!retry.success || retry.skipped?.includes(spaceId)) {
3689
- throw new Error(
3690
- `Failed to activate session after creating owned space ${spaceId}: ${retry.error ?? "space was skipped"}`
3766
+ throw Object.assign(
3767
+ new Error(
3768
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure2(retry)}`
3769
+ ),
3770
+ { cause: retry }
3691
3771
  );
3692
3772
  }
3693
3773
  this.confirmedHostedSpaceIds.add(spaceId);
@@ -3722,19 +3802,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3722
3802
  if (!host) {
3723
3803
  throw new Error("Owned space hosting requires a TinyCloud host");
3724
3804
  }
3725
- const hosted = await this.auth.hostOwnedSpace(
3726
- spaceId
3727
- );
3728
- if (!hosted) {
3729
- 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
+ );
3730
3811
  }
3731
3812
  const activation = await activateSessionWithHost2(
3732
3813
  host,
3733
3814
  session.delegationHeader
3734
3815
  );
3735
3816
  if (!activation.success || activation.skipped?.includes(spaceId)) {
3736
- throw new Error(
3737
- `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 }
3738
3822
  );
3739
3823
  }
3740
3824
  try {
@@ -3930,23 +4014,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3930
4014
  throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3931
4015
  }
3932
4016
  })();
3933
- const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
3934
- if (!Array.isArray(exactRecap) || !exactRecap.every(
3935
- (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(
3936
- (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
3937
- )
3938
- )) {
3939
- throw new UnsupportedSessionRestoreError(
3940
- "it cannot reconstruct caveat-preserving persisted ReCap authority"
3941
- );
3942
- }
3943
- stagedRecap = exactRecap.map((entry) => ({
3944
- service: entry.service,
3945
- space: entry.space,
3946
- path: entry.path,
3947
- actions: [...entry.actions],
3948
- caveats: cloneRecapCaveats(entry.caveats)
3949
- }));
4017
+ stagedRecap = exactVerifiedRecap(verified);
3950
4018
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3951
4019
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3952
4020
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
@@ -4750,7 +4818,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4750
4818
  if (cached) return cached;
4751
4819
  const response = await fetch(`${this.config.host}/info`);
4752
4820
  if (!response.ok) {
4753
- throw new Error(`Failed to fetch node info: HTTP ${response.status}`);
4821
+ throw await httpResponseError(response, "Failed to fetch node info");
4754
4822
  }
4755
4823
  const info = await response.json();
4756
4824
  if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
@@ -4800,9 +4868,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4800
4868
  );
4801
4869
  if (response.status === 404) return null;
4802
4870
  if (!response.ok) {
4803
- throw new Error(
4804
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
4805
- );
4871
+ throw await httpResponseError(response, `Failed to fetch encryption network ${networkId}`);
4806
4872
  }
4807
4873
  const body = await response.json();
4808
4874
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
@@ -5235,7 +5301,10 @@ var _TinyCloudNode = class _TinyCloudNode {
5235
5301
  const activation = await activateSessionWithHost2(host, delegationSession.delegationHeader);
5236
5302
  assertOwnerGraphActive();
5237
5303
  if (!activation.success) {
5238
- throw new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim());
5304
+ throw Object.assign(
5305
+ new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim()),
5306
+ { cause: activation }
5307
+ );
5239
5308
  }
5240
5309
  const delegation = {
5241
5310
  cid: delegationSession.delegationCid,
@@ -5300,7 +5369,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5300
5369
  headers: { accept: "application/json", "content-type": "application/json", authorization },
5301
5370
  body: canonicalizeEncryptionJson(request)
5302
5371
  });
5303
- 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");
5304
5373
  const verified = validateShareDeliveryAuthorizationV3Bytes(new Uint8Array(await response.arrayBuffer()), {
5305
5374
  request,
5306
5375
  senderKeyDid: this.credentialHolderDid,
@@ -5522,9 +5591,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5522
5591
  );
5523
5592
  }
5524
5593
  if (!response.ok) {
5525
- throw new Error(
5526
- `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
5527
- );
5594
+ throw await httpResponseError(response, `Failed to create encryption network ${networkId}`);
5528
5595
  }
5529
5596
  const created = await response.json();
5530
5597
  return created.descriptor;
@@ -5808,6 +5875,42 @@ var _TinyCloudNode = class _TinyCloudNode {
5808
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"))
5809
5876
  );
5810
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
+ }
5811
5914
  /**
5812
5915
  * Install a portable runtime permission delegation into this SDK instance so
5813
5916
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -5832,8 +5935,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5832
5935
  delegation.delegationHeader
5833
5936
  );
5834
5937
  if (!activateResult.success) {
5835
- throw new Error(
5836
- `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 }
5837
5941
  );
5838
5942
  }
5839
5943
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
@@ -5929,8 +6033,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5929
6033
  delegatedSession.delegationHeader
5930
6034
  );
5931
6035
  if (!activateResult.success) {
5932
- throw new Error(
5933
- `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 }
5934
6039
  );
5935
6040
  }
5936
6041
  const delegation = this.runtimeDelegationFromSession(
@@ -5938,7 +6043,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5938
6043
  delegatedEntries,
5939
6044
  spaceId,
5940
6045
  session,
5941
- expiresAt
6046
+ expiresAt,
6047
+ { siwe: prepared.siwe, signature }
5942
6048
  );
5943
6049
  this.runtimePermissionGrants.push({
5944
6050
  session: {
@@ -6115,7 +6221,10 @@ var _TinyCloudNode = class _TinyCloudNode {
6115
6221
  delegationSession.delegationHeader
6116
6222
  );
6117
6223
  if (!activateResult.success) {
6118
- 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
+ );
6119
6228
  }
6120
6229
  if (this._capabilityRegistry && this.session) {
6121
6230
  const sessionKey = {
@@ -6468,8 +6577,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6468
6577
  delegationHeader
6469
6578
  );
6470
6579
  if (!activateResult.success) {
6471
- throw new Error(
6472
- `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 }
6473
6583
  );
6474
6584
  }
6475
6585
  return {
@@ -6505,8 +6615,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6505
6615
  delegationHeader
6506
6616
  );
6507
6617
  if (!activateResult.success) {
6508
- throw new Error(
6509
- `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 }
6510
6621
  );
6511
6622
  }
6512
6623
  return {
@@ -6719,7 +6830,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6719
6830
  }
6720
6831
  return grants;
6721
6832
  }
6722
- runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
6833
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt, siweProof) {
6723
6834
  const resources = this.delegatedResourcesForEntries(entries, spaceId);
6724
6835
  const primary = resources[0];
6725
6836
  return {
@@ -6734,7 +6845,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6734
6845
  delegateDID: session.verificationMethod,
6735
6846
  ownerAddress: session.address,
6736
6847
  chainId: session.chainId,
6737
- host: this.config.host
6848
+ host: this.config.host,
6849
+ siweProof
6738
6850
  };
6739
6851
  }
6740
6852
  runtimeGrantFromDelegation(delegation, session) {
@@ -6927,6 +7039,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6927
7039
  if (grantedAction === requestedAction) {
6928
7040
  return true;
6929
7041
  }
7042
+ if (isExplicitOnlyAction(requestedAction)) {
7043
+ return false;
7044
+ }
6930
7045
  if (grantedAction.endsWith("/*")) {
6931
7046
  const prefix = grantedAction.slice(0, -2);
6932
7047
  return requestedAction.startsWith(`${prefix}/`);
@@ -7113,7 +7228,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7113
7228
  delegationSession.delegationHeader
7114
7229
  );
7115
7230
  if (!activateResult.success) {
7116
- 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
+ );
7117
7235
  }
7118
7236
  const result = {
7119
7237
  cid: delegationSession.delegationCid,
@@ -7263,7 +7381,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7263
7381
  invokerSession.delegationHeader
7264
7382
  );
7265
7383
  if (!activateResult.success) {
7266
- 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
+ );
7267
7388
  }
7268
7389
  const session = {
7269
7390
  address: mySession.address,
@@ -7380,7 +7501,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7380
7501
  subDelegationSession.delegationHeader
7381
7502
  );
7382
7503
  if (!activateResult.success) {
7383
- 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
+ );
7384
7508
  }
7385
7509
  return {
7386
7510
  cid: subDelegationSession.delegationCid,
@@ -7445,6 +7569,16 @@ import {
7445
7569
  decodeBase64Url,
7446
7570
  principalDidEquals as principalDidEquals3
7447
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
+ }
7448
7582
  var SERVICE_BY_SHORT_NAME = {
7449
7583
  kv: "tinycloud.kv",
7450
7584
  sql: "tinycloud.sql",
@@ -7591,6 +7725,15 @@ function signedAuthorityFromCompactUcan(authorization) {
7591
7725
  abilities
7592
7726
  ));
7593
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) {
7594
7737
  if (permissions.length === 0) {
7595
7738
  throw new Error("Validated runtime delegation has no signed capabilities.");
7596
7739
  }
@@ -7602,15 +7745,84 @@ function signedAuthorityFromCompactUcan(authorization) {
7602
7745
  actions: [...permission.actions],
7603
7746
  ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
7604
7747
  }));
7605
- return {
7606
- issuer: payload.iss,
7607
- audience: payload.aud,
7608
- expiry,
7609
- proofs: [...payload.prf],
7610
- ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
7611
- permissions: canonicalPermissions,
7612
- resources: canonicalResources
7613
- };
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()]);
7614
7826
  }
7615
7827
  function canonicalResourcesFromPortableDelegation(delegation) {
7616
7828
  if (delegation.resources !== void 0 && delegation.resources.length > 0) {
@@ -7646,46 +7858,58 @@ function resourcesMatch(actual, expected) {
7646
7858
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
7647
7859
  const host = normalizedHost(options.host);
7648
7860
  if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
7649
- throw new Error(
7861
+ reject(
7862
+ "host_mismatch",
7650
7863
  `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
7651
7864
  );
7652
7865
  }
7653
7866
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
7654
- throw new Error("Runtime delegation has an invalid expiry.");
7867
+ reject("authority_invalid", "Runtime delegation has an invalid expiry.");
7655
7868
  }
7656
7869
  if (delegation.expiry.getTime() <= Date.now()) {
7657
- throw new Error("Runtime delegation is expired.");
7870
+ reject("expired", "Runtime delegation is expired.");
7658
7871
  }
7659
7872
  const authorization = delegation.delegationHeader?.Authorization;
7660
7873
  if (typeof authorization !== "string" || authorization.length === 0) {
7661
- throw new Error("Runtime delegation is missing authorization bytes.");
7874
+ reject("authority_invalid", "Runtime delegation is missing authorization bytes.");
7662
7875
  }
7663
7876
  const cid = node.computeDelegationCid(authorization);
7664
7877
  if (cid !== delegation.cid) {
7665
- 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));
7666
7887
  }
7667
- const signed = signedAuthorityFromCompactUcan(authorization);
7668
7888
  if (signed.expiry.getTime() <= Date.now()) {
7669
- throw new Error("Runtime delegation is expired.");
7889
+ reject("expired", "Runtime delegation is expired.");
7670
7890
  }
7671
7891
  if (delegation.expiry.getTime() !== signed.expiry.getTime()) {
7672
- throw new Error("Runtime delegation expiry does not match signed authority.");
7892
+ reject("authority_invalid", "Runtime delegation expiry does not match signed authority.");
7673
7893
  }
7674
7894
  if (!didPrincipalsMatch(signed.audience, node.sessionDid)) {
7675
- throw new Error(
7895
+ reject(
7896
+ "audience_mismatch",
7676
7897
  `Runtime delegation targets ${signed.audience} but this session is ${node.sessionDid}.`
7677
7898
  );
7678
7899
  }
7679
7900
  if (!didPrincipalsMatch(delegation.delegateDID, signed.audience)) {
7680
- 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.");
7681
7905
  }
7682
- const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
7683
- if (!resourcesMatch(declaredResources, signed.resources)) {
7684
- 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.");
7685
7908
  }
7686
7909
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7687
7910
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
7688
- throw new Error(
7911
+ reject(
7912
+ "cid_mismatch",
7689
7913
  "A different authorization is already installed for this runtime delegation CID."
7690
7914
  );
7691
7915
  }
@@ -7715,22 +7939,28 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
7715
7939
  })),
7716
7940
  expiry: new Date(signed.expiry),
7717
7941
  delegateDID: signed.audience,
7718
- host
7942
+ host,
7943
+ ...compact ? {} : { siweProof: delegation.siweProof }
7719
7944
  };
7720
- if (node.isSessionOnly === true && typeof node.did === "string" && principalDidEquals3(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7721
- await node.useDelegation(installedCandidate);
7722
- } else {
7723
- 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));
7724
7953
  }
7725
7954
  const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7726
7955
  if (!installed) {
7727
- 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.");
7728
7957
  }
7729
7958
  if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed.audience) || installed.expiry.getTime() !== signed.expiry.getTime() || !resourcesMatch(
7730
7959
  canonicalResourcesFromPortableDelegation(installed),
7731
7960
  signed.resources
7732
7961
  ) || normalizedHost(installed.host ?? "") !== host) {
7733
- throw new Error(
7962
+ reject(
7963
+ "activation_rejected",
7734
7964
  "Runtime delegation activation installed authority that differs from the validated authority."
7735
7965
  );
7736
7966
  }