@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.cjs CHANGED
@@ -336,6 +336,10 @@ var defaultSignStrategy = { type: "auto-sign" };
336
336
  // src/authorization/NodeUserAuthorization.ts
337
337
  var DECRYPT_ACTION = import_sdk_core3.ENCRYPTION.DECRYPT;
338
338
  var NETWORK_CREATE_ACTION = import_sdk_core3.ENCRYPTION.NETWORK_CREATE;
339
+ function describeHostFailure(result) {
340
+ const text = result.error?.trim();
341
+ return text ? `${result.status} - ${text}` : `${result.status}`;
342
+ }
339
343
  function canonicalizeOpenKeyManifestJson(value) {
340
344
  const json = JSON.stringify(value);
341
345
  if (json === void 0) {
@@ -800,21 +804,28 @@ var NodeUserAuthorization = class {
800
804
  });
801
805
  const signature = await this.signMessage(siwe, purpose);
802
806
  const headers = this.wasm.siweToDelegationHeaders({ siwe, signature });
803
- const result = await (0, import_sdk_core3.submitHostDelegation)(host, headers);
804
- return result.success;
807
+ return (0, import_sdk_core3.submitHostDelegation)(host, headers);
805
808
  }
806
809
  /**
807
810
  * Create a specific space on the server via host delegation.
808
811
  * Used for lazy creation of additional spaces (e.g., public).
809
812
  */
810
813
  async hostPublicSpace(spaceId) {
811
- return this.hostSpace(spaceId);
814
+ return (await this.hostSpace(spaceId)).success;
812
815
  }
813
816
  /**
814
817
  * Create a specific owned space on the server via host delegation.
815
818
  * Used by manifest registry setup for the account space.
816
819
  */
817
820
  async hostOwnedSpace(spaceId, purpose) {
821
+ return (await this.hostOwnedSpaceResult(spaceId, purpose)).success;
822
+ }
823
+ /**
824
+ * {@link hostOwnedSpace} with the full host-delegation result, so a failure
825
+ * keeps its HTTP status (e.g. a typed 401/403) instead of collapsing to
826
+ * `false`.
827
+ */
828
+ async hostOwnedSpaceResult(spaceId, purpose) {
818
829
  return this.hostSpace(spaceId, purpose);
819
830
  }
820
831
  /**
@@ -858,9 +869,11 @@ var NodeUserAuthorization = class {
858
869
  }
859
870
  try {
860
871
  const created = await this.hostSpace();
861
- if (!created) {
862
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
863
- handler.onSpaceCreationFailed?.(creationContext, err);
872
+ if (!created.success) {
873
+ const err = Object.assign(
874
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
875
+ { cause: created }
876
+ );
864
877
  throw err;
865
878
  }
866
879
  } catch (error) {
@@ -876,8 +889,9 @@ var NodeUserAuthorization = class {
876
889
  this._tinyCloudSession.delegationHeader
877
890
  );
878
891
  if (!retryResult.success) {
879
- const err = new Error(
880
- `Failed to activate session after creating space: ${retryResult.error}`
892
+ const err = Object.assign(
893
+ new Error(`Failed to activate session after creating space: ${describeHostFailure(retryResult)}`),
894
+ { cause: retryResult }
881
895
  );
882
896
  handler.onSpaceCreationFailed?.(creationContext, err);
883
897
  throw err;
@@ -895,9 +909,11 @@ var NodeUserAuthorization = class {
895
909
  }
896
910
  try {
897
911
  const created = await this.hostSpace();
898
- if (!created) {
899
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
900
- handler.onSpaceCreationFailed?.(creationContext, err);
912
+ if (!created.success) {
913
+ const err = Object.assign(
914
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
915
+ { cause: created }
916
+ );
901
917
  throw err;
902
918
  }
903
919
  } catch (error) {
@@ -913,8 +929,9 @@ var NodeUserAuthorization = class {
913
929
  this._tinyCloudSession.delegationHeader
914
930
  );
915
931
  if (!retryResult.success) {
916
- const err = new Error(
917
- `Failed to activate session after creating space: ${retryResult.error}`
932
+ const err = Object.assign(
933
+ new Error(`Failed to activate session after creating space: ${describeHostFailure(retryResult)}`),
934
+ { cause: retryResult }
918
935
  );
919
936
  handler.onSpaceCreationFailed?.(creationContext, err);
920
937
  throw err;
@@ -922,7 +939,10 @@ var NodeUserAuthorization = class {
922
939
  handler.onSpaceCreated?.(creationContext);
923
940
  return;
924
941
  }
925
- throw new Error(`Failed to activate session: ${result.error}`);
942
+ throw Object.assign(
943
+ new Error(`Failed to activate session: ${describeHostFailure(result)}`),
944
+ { cause: result }
945
+ );
926
946
  }
927
947
  recordActivationSkippedSpaces(result, primarySpaceId) {
928
948
  if (result.success) {
@@ -1712,14 +1732,14 @@ var NodeUserAuthorization = class {
1712
1732
  * Request signature via event emitter with timeout.
1713
1733
  */
1714
1734
  requestSignatureViaEmitter(request, emitter, timeout) {
1715
- return new Promise((resolve, reject) => {
1735
+ return new Promise((resolve, reject2) => {
1716
1736
  const timeoutId = setTimeout(() => {
1717
- reject(new Error("Sign request timed out"));
1737
+ reject2(new Error("Sign request timed out"));
1718
1738
  }, timeout);
1719
1739
  const respond = async (response) => {
1720
1740
  clearTimeout(timeoutId);
1721
1741
  if (!response.approved) {
1722
- reject(
1742
+ reject2(
1723
1743
  new Error(response.reason ?? "Sign request rejected via emitter")
1724
1744
  );
1725
1745
  } else {
@@ -2302,6 +2322,25 @@ function jsonFromVerifier(value) {
2302
2322
  function verifierRecapEntries(entries) {
2303
2323
  return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
2304
2324
  }
2325
+ function exactVerifiedRecap(verified) {
2326
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
2327
+ if (!Array.isArray(exactRecap) || !exactRecap.every(
2328
+ (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(
2329
+ (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
2330
+ )
2331
+ )) {
2332
+ throw new UnsupportedSessionRestoreError(
2333
+ "it cannot reconstruct caveat-preserving persisted ReCap authority"
2334
+ );
2335
+ }
2336
+ return exactRecap.map((entry) => ({
2337
+ service: entry.service,
2338
+ space: entry.space,
2339
+ path: entry.path,
2340
+ actions: [...entry.actions],
2341
+ caveats: cloneRecapCaveats(entry.caveats)
2342
+ }));
2343
+ }
2305
2344
  var ServiceGraphLifetime = class {
2306
2345
  constructor(invokeFn, invokeAnyFn, fetchFn) {
2307
2346
  this.invokeFn = invokeFn;
@@ -2397,6 +2436,10 @@ function persistedExpiry(value) {
2397
2436
  function sameInstant(left, right) {
2398
2437
  return left.getTime() === right.getTime();
2399
2438
  }
2439
+ function describeHostFailure2(result) {
2440
+ const text = result.error?.trim();
2441
+ return text ? `${result.status} - ${text}` : `${result.status}`;
2442
+ }
2400
2443
  function ownerDelegationPermissions(params) {
2401
2444
  const hasPermissions = Object.prototype.hasOwnProperty.call(params, "permissions");
2402
2445
  const hasLegacy = Object.prototype.hasOwnProperty.call(params, "path") || Object.prototype.hasOwnProperty.call(params, "actions");
@@ -3200,7 +3243,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3200
3243
  marker
3201
3244
  );
3202
3245
  if (!written.ok) {
3203
- throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
3246
+ throw Object.assign(
3247
+ new Error(`Failed to write bootstrap completion marker: ${written.error.message}`),
3248
+ { cause: written.error }
3249
+ );
3204
3250
  }
3205
3251
  }
3206
3252
  isAcceptedBootstrapCompletionMarker(value) {
@@ -3246,17 +3292,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3246
3292
  rawAbilities
3247
3293
  });
3248
3294
  } catch (err) {
3249
- throw new Error(
3250
- `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3295
+ throw Object.assign(
3296
+ new Error(
3297
+ `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3298
+ ),
3299
+ { cause: err }
3251
3300
  );
3252
3301
  }
3253
3302
  sessions.set(step.space, session);
3254
3303
  }
3255
3304
  for (const step of steps) {
3256
3305
  if (step.kind !== "host") continue;
3257
- const hosted = await auth.hostOwnedSpace(step.spaceId, "bootstrap-host");
3258
- if (!hosted) {
3259
- throw new Error(`Failed to host bootstrap space: ${step.spaceId}`);
3306
+ const hosted = await auth.hostOwnedSpaceResult(step.spaceId, "bootstrap-host");
3307
+ if (!hosted.success) {
3308
+ throw Object.assign(
3309
+ new Error(`Failed to host bootstrap space ${step.spaceId}: ${describeHostFailure2(hosted)}`),
3310
+ { cause: hosted }
3311
+ );
3260
3312
  }
3261
3313
  }
3262
3314
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -3268,8 +3320,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3268
3320
  }
3269
3321
  const activated = await (0, import_sdk_core8.activateSessionWithHost)(host, session.delegationHeader);
3270
3322
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
3271
- throw new Error(
3272
- `Failed to activate bootstrap session for ${step.spaceId}: ${activated.error ?? "space was skipped"}`
3323
+ throw Object.assign(
3324
+ new Error(
3325
+ `Failed to activate bootstrap session for ${step.spaceId}: ${activated.success ? "space was skipped" : describeHostFailure2(activated)}`
3326
+ ),
3327
+ { cause: activated }
3273
3328
  );
3274
3329
  }
3275
3330
  this.registerBootstrapRuntimeGrant(
@@ -3282,7 +3337,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3282
3337
  if (step.kind === "account-index-schema") {
3283
3338
  const ensured = await this.account.index.ensure();
3284
3339
  if (!ensured.ok) {
3285
- throw new Error(`Failed to create account index schema: ${ensured.error.message}`);
3340
+ throw Object.assign(new Error(`Failed to create account index schema: ${ensured.error.message}`), { cause: ensured.error });
3286
3341
  }
3287
3342
  }
3288
3343
  if (step.kind === "seed-spaces") {
@@ -3297,8 +3352,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3297
3352
  }))
3298
3353
  );
3299
3354
  if (!registered.ok) {
3300
- throw new Error(
3301
- `Failed to seed account spaces: ${registered.error.message}`
3355
+ throw Object.assign(
3356
+ new Error(`Failed to seed account spaces: ${registered.error.message}`),
3357
+ { cause: registered.error }
3302
3358
  );
3303
3359
  }
3304
3360
  const batchError = registered.data.recoveredFromBatchError;
@@ -3319,7 +3375,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3319
3375
  { assumeUnregistered: true }
3320
3376
  );
3321
3377
  if (!registered.ok) {
3322
- throw new Error(`Failed to seed bootstrap applications: ${registered.error.message}`);
3378
+ throw Object.assign(new Error(`Failed to seed bootstrap applications: ${registered.error.message}`), { cause: registered.error });
3323
3379
  }
3324
3380
  }
3325
3381
  if (step.kind === "encryption-network-create") {
@@ -3337,8 +3393,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3337
3393
  ]
3338
3394
  });
3339
3395
  if (!migrated.ok) {
3340
- throw new Error(
3341
- `Failed to create secret_records schema: ${migrated.error.message}`
3396
+ throw Object.assign(
3397
+ new Error(`Failed to create secret_records schema: ${migrated.error.message}`),
3398
+ { cause: migrated.error }
3342
3399
  );
3343
3400
  }
3344
3401
  }
@@ -3510,8 +3567,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3510
3567
  await this.ensureOwnedSpaceHostedById(accountSpaceId);
3511
3568
  const result = await this.account.applications.register(request.manifests);
3512
3569
  if (!result.ok) {
3513
- throw new Error(
3514
- `Failed to write manifest registry records: ${result.error.message}`
3570
+ throw Object.assign(
3571
+ new Error(`Failed to write manifest registry records: ${result.error.message}`),
3572
+ { cause: result.error }
3515
3573
  );
3516
3574
  }
3517
3575
  }
@@ -3536,7 +3594,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3536
3594
  if (this.currentSessionCanListSpaces()) {
3537
3595
  const spaces = await this.account.spaces.syncAccessible();
3538
3596
  if (!spaces.ok) {
3539
- throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
3597
+ throw Object.assign(
3598
+ new Error(`Failed to sync account spaces: ${spaces.error.message}`),
3599
+ { cause: spaces.error }
3600
+ );
3540
3601
  }
3541
3602
  }
3542
3603
  });
@@ -3591,14 +3652,20 @@ var _TinyCloudNode = class _TinyCloudNode {
3591
3652
  await task();
3592
3653
  return;
3593
3654
  } catch (error) {
3655
+ const verdict = (0, import_sdk_core8.authorizationVerdictOf)(error);
3594
3656
  const message = error instanceof Error ? error.message : String(error);
3595
- if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
3657
+ const isAuthorizationVerdict = verdict === void 0 ? /Unauthorized Action|\b(?:401|403)\b/.test(message) : verdict !== "other";
3658
+ if (isAuthorizationVerdict) {
3596
3659
  console.warn(
3597
3660
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
3598
3661
  error
3599
3662
  );
3600
3663
  return;
3601
3664
  }
3665
+ if ((0, import_sdk_core8.isStorageFullError)(error) || (0, import_sdk_core8.isStorageFullError)(error?.cause) || message.includes(import_sdk_core8.STORAGE_FULL_MESSAGE) || message.includes(import_sdk_core8.STORAGE_WRITE_TOO_LARGE_MESSAGE)) {
3666
+ console.warn("TinyCloud account registry sync stopped: storage is full", error);
3667
+ return;
3668
+ }
3602
3669
  lastError = error;
3603
3670
  if (attempt < delays.length - 1) {
3604
3671
  await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
@@ -3672,19 +3739,26 @@ var _TinyCloudNode = class _TinyCloudNode {
3672
3739
  return;
3673
3740
  }
3674
3741
  if (!activation.success && activation.status !== 404) {
3675
- throw new Error(
3676
- `Failed to check owned space ${spaceId}: ${activation.error ?? activation.status}`
3742
+ throw Object.assign(
3743
+ new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure2(activation)}`),
3744
+ { cause: activation }
3677
3745
  );
3678
3746
  }
3679
- const created = await this.auth.hostOwnedSpace(spaceId);
3680
- if (!created) {
3681
- throw new Error(`Failed to create owned space: ${spaceId}`);
3747
+ const created = await this.auth.hostOwnedSpaceResult(spaceId);
3748
+ if (!created.success) {
3749
+ throw Object.assign(
3750
+ new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
3751
+ { cause: created }
3752
+ );
3682
3753
  }
3683
3754
  await new Promise((resolve) => setTimeout(resolve, 100));
3684
3755
  const retry = await (0, import_sdk_core8.activateSessionWithHost)(host, session.delegationHeader);
3685
3756
  if (!retry.success || retry.skipped?.includes(spaceId)) {
3686
- throw new Error(
3687
- `Failed to activate session after creating owned space ${spaceId}: ${retry.error ?? "space was skipped"}`
3757
+ throw Object.assign(
3758
+ new Error(
3759
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure2(retry)}`
3760
+ ),
3761
+ { cause: retry }
3688
3762
  );
3689
3763
  }
3690
3764
  this.confirmedHostedSpaceIds.add(spaceId);
@@ -3719,19 +3793,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3719
3793
  if (!host) {
3720
3794
  throw new Error("Owned space hosting requires a TinyCloud host");
3721
3795
  }
3722
- const hosted = await this.auth.hostOwnedSpace(
3723
- spaceId
3724
- );
3725
- if (!hosted) {
3726
- throw new Error(`Failed to host owned space: ${spaceId}`);
3796
+ const hosted = await this.auth.hostOwnedSpaceResult(spaceId);
3797
+ if (!hosted.success) {
3798
+ throw Object.assign(
3799
+ new Error(`Failed to host owned space ${spaceId}: ${describeHostFailure2(hosted)}`),
3800
+ { cause: hosted }
3801
+ );
3727
3802
  }
3728
3803
  const activation = await (0, import_sdk_core8.activateSessionWithHost)(
3729
3804
  host,
3730
3805
  session.delegationHeader
3731
3806
  );
3732
3807
  if (!activation.success || activation.skipped?.includes(spaceId)) {
3733
- throw new Error(
3734
- `Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
3808
+ throw Object.assign(
3809
+ new Error(
3810
+ `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
3811
+ ),
3812
+ { cause: activation }
3735
3813
  );
3736
3814
  }
3737
3815
  try {
@@ -3927,23 +4005,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3927
4005
  throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3928
4006
  }
3929
4007
  })();
3930
- const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
3931
- if (!Array.isArray(exactRecap) || !exactRecap.every(
3932
- (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(
3933
- (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
3934
- )
3935
- )) {
3936
- throw new UnsupportedSessionRestoreError(
3937
- "it cannot reconstruct caveat-preserving persisted ReCap authority"
3938
- );
3939
- }
3940
- stagedRecap = exactRecap.map((entry) => ({
3941
- service: entry.service,
3942
- space: entry.space,
3943
- path: entry.path,
3944
- actions: [...entry.actions],
3945
- caveats: cloneRecapCaveats(entry.caveats)
3946
- }));
4008
+ stagedRecap = exactVerifiedRecap(verified);
3947
4009
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3948
4010
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3949
4011
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
@@ -4747,7 +4809,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4747
4809
  if (cached) return cached;
4748
4810
  const response = await fetch(`${this.config.host}/info`);
4749
4811
  if (!response.ok) {
4750
- throw new Error(`Failed to fetch node info: HTTP ${response.status}`);
4812
+ throw await (0, import_sdk_core8.httpResponseError)(response, "Failed to fetch node info");
4751
4813
  }
4752
4814
  const info = await response.json();
4753
4815
  if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
@@ -4797,9 +4859,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4797
4859
  );
4798
4860
  if (response.status === 404) return null;
4799
4861
  if (!response.ok) {
4800
- throw new Error(
4801
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
4802
- );
4862
+ throw await (0, import_sdk_core8.httpResponseError)(response, `Failed to fetch encryption network ${networkId}`);
4803
4863
  }
4804
4864
  const body = await response.json();
4805
4865
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
@@ -5232,7 +5292,10 @@ var _TinyCloudNode = class _TinyCloudNode {
5232
5292
  const activation = await (0, import_sdk_core8.activateSessionWithHost)(host, delegationSession.delegationHeader);
5233
5293
  assertOwnerGraphActive();
5234
5294
  if (!activation.success) {
5235
- throw new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim());
5295
+ throw Object.assign(
5296
+ new Error(`Owner delegation import failed: ${activation.status} ${activation.error ?? ""}`.trim()),
5297
+ { cause: activation }
5298
+ );
5236
5299
  }
5237
5300
  const delegation = {
5238
5301
  cid: delegationSession.delegationCid,
@@ -5297,7 +5360,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5297
5360
  headers: { accept: "application/json", "content-type": "application/json", authorization },
5298
5361
  body: (0, import_sdk_core8.canonicalizeEncryptionJson)(request)
5299
5362
  });
5300
- if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
5363
+ if (!response.ok) throw await (0, import_sdk_core8.httpResponseError)(response, "V3 share delivery authorization failed");
5301
5364
  const verified = (0, import_sdk_core8.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
5302
5365
  request,
5303
5366
  senderKeyDid: this.credentialHolderDid,
@@ -5519,9 +5582,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5519
5582
  );
5520
5583
  }
5521
5584
  if (!response.ok) {
5522
- throw new Error(
5523
- `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
5524
- );
5585
+ throw await (0, import_sdk_core8.httpResponseError)(response, `Failed to create encryption network ${networkId}`);
5525
5586
  }
5526
5587
  const created = await response.json();
5527
5588
  return created.descriptor;
@@ -5805,6 +5866,42 @@ var _TinyCloudNode = class _TinyCloudNode {
5805
5866
  (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"))
5806
5867
  );
5807
5868
  }
5869
+ /**
5870
+ * Verify a wallet-signed session grant (a CACAO built from a SIWE and its
5871
+ * EIP-191 signature) issued to this instance's live session key. The
5872
+ * verifier rebuilds the CACAO from `siwe` and `signature`, so the grant is
5873
+ * accepted only when its authorization bytes and CID are exactly that CACAO,
5874
+ * its signer is `address`, its audience is this session key, it is valid
5875
+ * now, and its ReCap covers `spaceId` or names only raw encryption networks
5876
+ * (a decrypt-only grant has no space). Returns the signed ReCap and expiry.
5877
+ * Bindings without `validateSessionGrant` fall back to the session verifier,
5878
+ * which also refuses decrypt-only grants.
5879
+ */
5880
+ verifySessionGrant(grant) {
5881
+ const bindings = this.wasmBindings;
5882
+ const validate = typeof bindings.validateSessionGrant === "function" ? bindings.validateSessionGrant.bind(bindings) : bindings.validatePersistedSession?.bind(bindings);
5883
+ if (validate === void 0) {
5884
+ throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
5885
+ }
5886
+ const verified = validate({
5887
+ delegationHeader: { Authorization: grant.delegationHeader.Authorization },
5888
+ delegationCid: grant.delegationCid,
5889
+ spaceId: grant.spaceId,
5890
+ jwk: this.sessionKeyJwk,
5891
+ address: (0, import_sdk_core8.canonicalizeAddress)(grant.address),
5892
+ chainId: grant.chainId,
5893
+ siwe: grant.siwe,
5894
+ signature: grant.signature
5895
+ });
5896
+ if (typeof verified.expiresAt !== "string") {
5897
+ throw new Error("Session grant has no signed expiry.");
5898
+ }
5899
+ const expiresAt = new Date(verified.expiresAt);
5900
+ if (!Number.isFinite(expiresAt.getTime())) {
5901
+ throw new Error("Session grant has an invalid signed expiry.");
5902
+ }
5903
+ return { recap: exactVerifiedRecap(verified), expiresAt };
5904
+ }
5808
5905
  /**
5809
5906
  * Install a portable runtime permission delegation into this SDK instance so
5810
5907
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -5829,8 +5926,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5829
5926
  delegation.delegationHeader
5830
5927
  );
5831
5928
  if (!activateResult.success) {
5832
- throw new Error(
5833
- `Failed to activate runtime permission delegation: ${activateResult.error}`
5929
+ throw Object.assign(
5930
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
5931
+ { cause: activateResult }
5834
5932
  );
5835
5933
  }
5836
5934
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
@@ -5926,8 +6024,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5926
6024
  delegatedSession.delegationHeader
5927
6025
  );
5928
6026
  if (!activateResult.success) {
5929
- throw new Error(
5930
- `Failed to activate runtime permission delegation: ${activateResult.error}`
6027
+ throw Object.assign(
6028
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
6029
+ { cause: activateResult }
5931
6030
  );
5932
6031
  }
5933
6032
  const delegation = this.runtimeDelegationFromSession(
@@ -5935,7 +6034,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5935
6034
  delegatedEntries,
5936
6035
  spaceId,
5937
6036
  session,
5938
- expiresAt
6037
+ expiresAt,
6038
+ { siwe: prepared.siwe, signature }
5939
6039
  );
5940
6040
  this.runtimePermissionGrants.push({
5941
6041
  session: {
@@ -6112,7 +6212,10 @@ var _TinyCloudNode = class _TinyCloudNode {
6112
6212
  delegationSession.delegationHeader
6113
6213
  );
6114
6214
  if (!activateResult.success) {
6115
- throw new Error(`Failed to activate public space delegation: ${activateResult.error}`);
6215
+ throw Object.assign(
6216
+ new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
6217
+ { cause: activateResult }
6218
+ );
6116
6219
  }
6117
6220
  if (this._capabilityRegistry && this.session) {
6118
6221
  const sessionKey = {
@@ -6465,8 +6568,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6465
6568
  delegationHeader
6466
6569
  );
6467
6570
  if (!activateResult.success) {
6468
- throw new Error(
6469
- `Failed to activate delegation with host: ${activateResult.error}`
6571
+ throw Object.assign(
6572
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6573
+ { cause: activateResult }
6470
6574
  );
6471
6575
  }
6472
6576
  return {
@@ -6502,8 +6606,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6502
6606
  delegationHeader
6503
6607
  );
6504
6608
  if (!activateResult.success) {
6505
- throw new Error(
6506
- `Failed to activate delegation with host: ${activateResult.error}`
6609
+ throw Object.assign(
6610
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6611
+ { cause: activateResult }
6507
6612
  );
6508
6613
  }
6509
6614
  return {
@@ -6716,7 +6821,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6716
6821
  }
6717
6822
  return grants;
6718
6823
  }
6719
- runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
6824
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt, siweProof) {
6720
6825
  const resources = this.delegatedResourcesForEntries(entries, spaceId);
6721
6826
  const primary = resources[0];
6722
6827
  return {
@@ -6731,7 +6836,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6731
6836
  delegateDID: session.verificationMethod,
6732
6837
  ownerAddress: session.address,
6733
6838
  chainId: session.chainId,
6734
- host: this.config.host
6839
+ host: this.config.host,
6840
+ siweProof
6735
6841
  };
6736
6842
  }
6737
6843
  runtimeGrantFromDelegation(delegation, session) {
@@ -6924,6 +7030,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6924
7030
  if (grantedAction === requestedAction) {
6925
7031
  return true;
6926
7032
  }
7033
+ if ((0, import_sdk_core8.isExplicitOnlyAction)(requestedAction)) {
7034
+ return false;
7035
+ }
6927
7036
  if (grantedAction.endsWith("/*")) {
6928
7037
  const prefix = grantedAction.slice(0, -2);
6929
7038
  return requestedAction.startsWith(`${prefix}/`);
@@ -7110,7 +7219,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7110
7219
  delegationSession.delegationHeader
7111
7220
  );
7112
7221
  if (!activateResult.success) {
7113
- throw new Error(`Failed to activate delegation: ${activateResult.error}`);
7222
+ throw Object.assign(
7223
+ new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
7224
+ { cause: activateResult }
7225
+ );
7114
7226
  }
7115
7227
  const result = {
7116
7228
  cid: delegationSession.delegationCid,
@@ -7260,7 +7372,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7260
7372
  invokerSession.delegationHeader
7261
7373
  );
7262
7374
  if (!activateResult.success) {
7263
- throw new Error(`Failed to activate delegated session: ${activateResult.error}`);
7375
+ throw Object.assign(
7376
+ new Error(`Failed to activate delegated session: ${describeHostFailure2(activateResult)}`),
7377
+ { cause: activateResult }
7378
+ );
7264
7379
  }
7265
7380
  const session = {
7266
7381
  address: mySession.address,
@@ -7377,7 +7492,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7377
7492
  subDelegationSession.delegationHeader
7378
7493
  );
7379
7494
  if (!activateResult.success) {
7380
- throw new Error(`Failed to activate sub-delegation: ${activateResult.error}`);
7495
+ throw Object.assign(
7496
+ new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
7497
+ { cause: activateResult }
7498
+ );
7381
7499
  }
7382
7500
  return {
7383
7501
  cid: subDelegationSession.delegationCid,
@@ -7418,6 +7536,16 @@ var import_sdk_core12 = require("@tinycloud/sdk-core");
7418
7536
 
7419
7537
  // src/delegation.ts
7420
7538
  var import_sdk_core9 = require("@tinycloud/sdk-core");
7539
+ var RuntimeDelegationRejectedError = class extends Error {
7540
+ constructor(reason, message) {
7541
+ super(message);
7542
+ this.name = "RuntimeDelegationRejectedError";
7543
+ this.reason = reason;
7544
+ }
7545
+ };
7546
+ function reject(reason, message) {
7547
+ throw new RuntimeDelegationRejectedError(reason, message);
7548
+ }
7421
7549
  var SERVICE_BY_SHORT_NAME = {
7422
7550
  kv: "tinycloud.kv",
7423
7551
  sql: "tinycloud.sql",
@@ -7564,6 +7692,15 @@ function signedAuthorityFromCompactUcan(authorization) {
7564
7692
  abilities
7565
7693
  ));
7566
7694
  }
7695
+ return canonicalSignedAuthority({
7696
+ issuer: payload.iss,
7697
+ audience: payload.aud,
7698
+ expiry,
7699
+ proofs: [...payload.prf],
7700
+ ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth }
7701
+ }, permissions);
7702
+ }
7703
+ function canonicalSignedAuthority(signed, permissions) {
7567
7704
  if (permissions.length === 0) {
7568
7705
  throw new Error("Validated runtime delegation has no signed capabilities.");
7569
7706
  }
@@ -7575,15 +7712,84 @@ function signedAuthorityFromCompactUcan(authorization) {
7575
7712
  actions: [...permission.actions],
7576
7713
  ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
7577
7714
  }));
7578
- return {
7579
- issuer: payload.iss,
7580
- audience: payload.aud,
7581
- expiry,
7582
- proofs: [...payload.prf],
7583
- ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
7584
- permissions: canonicalPermissions,
7585
- resources: canonicalResources
7586
- };
7715
+ return { ...signed, permissions: canonicalPermissions, resources: canonicalResources };
7716
+ }
7717
+ var RAW_ENCRYPTION_PREFIX = "urn:tinycloud:encryption:";
7718
+ var PKH_SPACE = /^tinycloud:pkh:eip155:(\d+):(0x[0-9a-fA-F]{40}):(.+)$/;
7719
+ function comparableSpace(space) {
7720
+ const match = PKH_SPACE.exec(space);
7721
+ return match ? `tinycloud:pkh:eip155:${match[1]}:${match[2].toLowerCase()}:${match[3]}` : space;
7722
+ }
7723
+ function signedAuthorityFromSessionGrant(node, delegation, authorization, cid) {
7724
+ const proof = delegation.siweProof;
7725
+ if (proof === void 0 || proof === null || typeof proof !== "object" || typeof proof.siwe !== "string" || typeof proof.signature !== "string") {
7726
+ reject(
7727
+ "proof_missing",
7728
+ "Runtime delegation is a wallet-signed session grant stored without its signed SIWE proof, so its authority cannot be verified."
7729
+ );
7730
+ }
7731
+ if (typeof node.verifySessionGrant !== "function") {
7732
+ reject("unsupported", "This runtime cannot verify wallet-signed session grants.");
7733
+ }
7734
+ const owner = `did:pkh:eip155:${delegation.chainId}:${delegation.ownerAddress}`;
7735
+ if (typeof node.did !== "string" || !didPrincipalsMatch(owner, node.did)) {
7736
+ reject("owner_mismatch", "Runtime delegation is not signed by this session's owner.");
7737
+ }
7738
+ let verified;
7739
+ try {
7740
+ verified = node.verifySessionGrant({
7741
+ delegationHeader: { Authorization: authorization },
7742
+ delegationCid: cid,
7743
+ spaceId: delegation.spaceId,
7744
+ address: delegation.ownerAddress,
7745
+ chainId: delegation.chainId,
7746
+ siwe: proof.siwe,
7747
+ signature: proof.signature
7748
+ });
7749
+ } catch (error) {
7750
+ reject("proof_invalid", error instanceof Error ? error.message : String(error));
7751
+ }
7752
+ const space = comparableSpace(delegation.spaceId);
7753
+ const spaceOwner = PKH_SPACE.exec(delegation.spaceId);
7754
+ if (!spaceOwner || !didPrincipalsMatch(`did:pkh:eip155:${spaceOwner[1]}:${spaceOwner[2]}`, owner)) {
7755
+ reject("authority_invalid", "Runtime delegation space is not owned by its signer.");
7756
+ }
7757
+ const groups = /* @__PURE__ */ new Map();
7758
+ for (const entry of verified.recap) {
7759
+ const service = entry.service.startsWith("tinycloud.") ? entry.service : `tinycloud.${entry.service}`;
7760
+ const raw = entry.space === "encryption";
7761
+ if (raw) {
7762
+ if (service !== "tinycloud.encryption" || !entry.path.startsWith(RAW_ENCRYPTION_PREFIX)) {
7763
+ reject("authority_invalid", "Runtime delegation has an unsupported signed raw resource.");
7764
+ }
7765
+ const networkOwner = entry.path.slice(RAW_ENCRYPTION_PREFIX.length, entry.path.lastIndexOf(":"));
7766
+ if (!didPrincipalsMatch(networkOwner, owner)) {
7767
+ reject("authority_invalid", "Runtime delegation names an encryption network its signer does not own.");
7768
+ }
7769
+ } else if (service === "tinycloud.encryption" || comparableSpace(entry.space) !== space) {
7770
+ reject("authority_invalid", "Runtime delegation has signed authority outside its space.");
7771
+ }
7772
+ const caveats = signedCaveats(entry.caveats ?? []);
7773
+ const key = JSON.stringify([service, raw ? "" : entry.space, entry.path, (0, import_sdk_core9.canonicalizeRecapCaveats)(caveats)]);
7774
+ const group = groups.get(key) ?? {
7775
+ service,
7776
+ ...raw ? {} : { space: entry.space },
7777
+ path: entry.path,
7778
+ actions: [],
7779
+ ...caveats === void 0 ? {} : { caveats }
7780
+ };
7781
+ for (const action of entry.actions) {
7782
+ if (!group.actions.includes(action)) group.actions.push(action);
7783
+ }
7784
+ groups.set(key, group);
7785
+ }
7786
+ return canonicalSignedAuthority({
7787
+ issuer: owner,
7788
+ // The verifier bound the SIWE audience to this runtime's own session key.
7789
+ audience: node.sessionDid,
7790
+ expiry: verified.expiresAt,
7791
+ proofs: []
7792
+ }, [...groups.values()]);
7587
7793
  }
7588
7794
  function canonicalResourcesFromPortableDelegation(delegation) {
7589
7795
  if (delegation.resources !== void 0 && delegation.resources.length > 0) {
@@ -7619,46 +7825,58 @@ function resourcesMatch(actual, expected) {
7619
7825
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
7620
7826
  const host = normalizedHost(options.host);
7621
7827
  if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
7622
- throw new Error(
7828
+ reject(
7829
+ "host_mismatch",
7623
7830
  `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
7624
7831
  );
7625
7832
  }
7626
7833
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
7627
- throw new Error("Runtime delegation has an invalid expiry.");
7834
+ reject("authority_invalid", "Runtime delegation has an invalid expiry.");
7628
7835
  }
7629
7836
  if (delegation.expiry.getTime() <= Date.now()) {
7630
- throw new Error("Runtime delegation is expired.");
7837
+ reject("expired", "Runtime delegation is expired.");
7631
7838
  }
7632
7839
  const authorization = delegation.delegationHeader?.Authorization;
7633
7840
  if (typeof authorization !== "string" || authorization.length === 0) {
7634
- throw new Error("Runtime delegation is missing authorization bytes.");
7841
+ reject("authority_invalid", "Runtime delegation is missing authorization bytes.");
7635
7842
  }
7636
7843
  const cid = node.computeDelegationCid(authorization);
7637
7844
  if (cid !== delegation.cid) {
7638
- throw new Error("Runtime delegation CID does not match authorization bytes.");
7845
+ reject("cid_mismatch", "Runtime delegation CID does not match authorization bytes.");
7846
+ }
7847
+ const compact = authorizationWithoutBearer(authorization).split(".").length === 3;
7848
+ let signed;
7849
+ try {
7850
+ signed = compact ? signedAuthorityFromCompactUcan(authorization) : signedAuthorityFromSessionGrant(node, delegation, authorization, cid);
7851
+ } catch (error) {
7852
+ if (error instanceof RuntimeDelegationRejectedError) throw error;
7853
+ reject("authority_invalid", error instanceof Error ? error.message : String(error));
7639
7854
  }
7640
- const signed = signedAuthorityFromCompactUcan(authorization);
7641
7855
  if (signed.expiry.getTime() <= Date.now()) {
7642
- throw new Error("Runtime delegation is expired.");
7856
+ reject("expired", "Runtime delegation is expired.");
7643
7857
  }
7644
7858
  if (delegation.expiry.getTime() !== signed.expiry.getTime()) {
7645
- throw new Error("Runtime delegation expiry does not match signed authority.");
7859
+ reject("authority_invalid", "Runtime delegation expiry does not match signed authority.");
7646
7860
  }
7647
7861
  if (!didPrincipalsMatch(signed.audience, node.sessionDid)) {
7648
- throw new Error(
7862
+ reject(
7863
+ "audience_mismatch",
7649
7864
  `Runtime delegation targets ${signed.audience} but this session is ${node.sessionDid}.`
7650
7865
  );
7651
7866
  }
7652
7867
  if (!didPrincipalsMatch(delegation.delegateDID, signed.audience)) {
7653
- throw new Error("Runtime delegation audience does not match signed authority.");
7868
+ reject("audience_mismatch", "Runtime delegation audience does not match signed authority.");
7869
+ }
7870
+ if (compact && !resourcesMatch(canonicalResourcesFromPortableDelegation(delegation), signed.resources)) {
7871
+ reject("resources_mismatch", "Runtime delegation resources do not match signed authority.");
7654
7872
  }
7655
- const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
7656
- if (!resourcesMatch(declaredResources, signed.resources)) {
7657
- throw new Error("Runtime delegation resources do not match signed authority.");
7873
+ if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
7874
+ throw new Error("Runtime delegation is not authorized for this runtime.");
7658
7875
  }
7659
7876
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7660
7877
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
7661
- throw new Error(
7878
+ reject(
7879
+ "cid_mismatch",
7662
7880
  "A different authorization is already installed for this runtime delegation CID."
7663
7881
  );
7664
7882
  }
@@ -7688,22 +7906,28 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
7688
7906
  })),
7689
7907
  expiry: new Date(signed.expiry),
7690
7908
  delegateDID: signed.audience,
7691
- host
7909
+ host,
7910
+ ...compact ? {} : { siweProof: delegation.siweProof }
7692
7911
  };
7693
- if (node.isSessionOnly === true && typeof node.did === "string" && (0, import_sdk_core9.principalDidEquals)(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7694
- await node.useDelegation(installedCandidate);
7695
- } else {
7696
- await node.useRuntimeDelegation(installedCandidate);
7912
+ try {
7913
+ if (node.isSessionOnly === true && typeof node.did === "string" && (0, import_sdk_core9.principalDidEquals)(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7914
+ await node.useDelegation(installedCandidate);
7915
+ } else {
7916
+ await node.useRuntimeDelegation(installedCandidate);
7917
+ }
7918
+ } catch (error) {
7919
+ reject("activation_rejected", error instanceof Error ? error.message : String(error));
7697
7920
  }
7698
7921
  const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7699
7922
  if (!installed) {
7700
- throw new Error("Runtime delegation activation did not install the validated authority.");
7923
+ reject("activation_rejected", "Runtime delegation activation did not install the validated authority.");
7701
7924
  }
7702
7925
  if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed.audience) || installed.expiry.getTime() !== signed.expiry.getTime() || !resourcesMatch(
7703
7926
  canonicalResourcesFromPortableDelegation(installed),
7704
7927
  signed.resources
7705
7928
  ) || normalizedHost(installed.host ?? "") !== host) {
7706
- throw new Error(
7929
+ reject(
7930
+ "activation_rejected",
7707
7931
  "Runtime delegation activation installed authority that differs from the validated authority."
7708
7932
  );
7709
7933
  }