@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.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) {
@@ -864,10 +868,12 @@ var NodeUserAuthorization = class {
864
868
  return;
865
869
  }
866
870
  try {
867
- const created = (await this.hostSpace()).success;
868
- if (!created) {
869
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
870
- handler.onSpaceCreationFailed?.(creationContext, err);
871
+ const created = await this.hostSpace();
872
+ if (!created.success) {
873
+ const err = Object.assign(
874
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
875
+ { cause: created }
876
+ );
871
877
  throw err;
872
878
  }
873
879
  } catch (error) {
@@ -883,8 +889,9 @@ var NodeUserAuthorization = class {
883
889
  this._tinyCloudSession.delegationHeader
884
890
  );
885
891
  if (!retryResult.success) {
886
- const err = new Error(
887
- `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 }
888
895
  );
889
896
  handler.onSpaceCreationFailed?.(creationContext, err);
890
897
  throw err;
@@ -901,10 +908,12 @@ var NodeUserAuthorization = class {
901
908
  return;
902
909
  }
903
910
  try {
904
- const created = (await this.hostSpace()).success;
905
- if (!created) {
906
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
907
- handler.onSpaceCreationFailed?.(creationContext, err);
911
+ const created = await this.hostSpace();
912
+ if (!created.success) {
913
+ const err = Object.assign(
914
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
915
+ { cause: created }
916
+ );
908
917
  throw err;
909
918
  }
910
919
  } catch (error) {
@@ -920,8 +929,9 @@ var NodeUserAuthorization = class {
920
929
  this._tinyCloudSession.delegationHeader
921
930
  );
922
931
  if (!retryResult.success) {
923
- const err = new Error(
924
- `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 }
925
935
  );
926
936
  handler.onSpaceCreationFailed?.(creationContext, err);
927
937
  throw err;
@@ -929,7 +939,10 @@ var NodeUserAuthorization = class {
929
939
  handler.onSpaceCreated?.(creationContext);
930
940
  return;
931
941
  }
932
- 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
+ );
933
946
  }
934
947
  recordActivationSkippedSpaces(result, primarySpaceId) {
935
948
  if (result.success) {
@@ -1719,14 +1732,14 @@ var NodeUserAuthorization = class {
1719
1732
  * Request signature via event emitter with timeout.
1720
1733
  */
1721
1734
  requestSignatureViaEmitter(request, emitter, timeout) {
1722
- return new Promise((resolve, reject) => {
1735
+ return new Promise((resolve, reject2) => {
1723
1736
  const timeoutId = setTimeout(() => {
1724
- reject(new Error("Sign request timed out"));
1737
+ reject2(new Error("Sign request timed out"));
1725
1738
  }, timeout);
1726
1739
  const respond = async (response) => {
1727
1740
  clearTimeout(timeoutId);
1728
1741
  if (!response.approved) {
1729
- reject(
1742
+ reject2(
1730
1743
  new Error(response.reason ?? "Sign request rejected via emitter")
1731
1744
  );
1732
1745
  } else {
@@ -2309,6 +2322,25 @@ function jsonFromVerifier(value) {
2309
2322
  function verifierRecapEntries(entries) {
2310
2323
  return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
2311
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
+ }
2312
2344
  var ServiceGraphLifetime = class {
2313
2345
  constructor(invokeFn, invokeAnyFn, fetchFn) {
2314
2346
  this.invokeFn = invokeFn;
@@ -2404,7 +2436,7 @@ function persistedExpiry(value) {
2404
2436
  function sameInstant(left, right) {
2405
2437
  return left.getTime() === right.getTime();
2406
2438
  }
2407
- function describeHostFailure(result) {
2439
+ function describeHostFailure2(result) {
2408
2440
  const text = result.error?.trim();
2409
2441
  return text ? `${result.status} - ${text}` : `${result.status}`;
2410
2442
  }
@@ -3211,7 +3243,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3211
3243
  marker
3212
3244
  );
3213
3245
  if (!written.ok) {
3214
- 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
+ );
3215
3250
  }
3216
3251
  }
3217
3252
  isAcceptedBootstrapCompletionMarker(value) {
@@ -3257,17 +3292,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3257
3292
  rawAbilities
3258
3293
  });
3259
3294
  } catch (err) {
3260
- throw new Error(
3261
- `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 }
3262
3300
  );
3263
3301
  }
3264
3302
  sessions.set(step.space, session);
3265
3303
  }
3266
3304
  for (const step of steps) {
3267
3305
  if (step.kind !== "host") continue;
3268
- const hosted = await auth.hostOwnedSpace(step.spaceId, "bootstrap-host");
3269
- if (!hosted) {
3270
- 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
+ );
3271
3312
  }
3272
3313
  }
3273
3314
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -3279,8 +3320,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3279
3320
  }
3280
3321
  const activated = await (0, import_sdk_core8.activateSessionWithHost)(host, session.delegationHeader);
3281
3322
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
3282
- throw new Error(
3283
- `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 }
3284
3328
  );
3285
3329
  }
3286
3330
  this.registerBootstrapRuntimeGrant(
@@ -3293,7 +3337,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3293
3337
  if (step.kind === "account-index-schema") {
3294
3338
  const ensured = await this.account.index.ensure();
3295
3339
  if (!ensured.ok) {
3296
- 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 });
3297
3341
  }
3298
3342
  }
3299
3343
  if (step.kind === "seed-spaces") {
@@ -3308,8 +3352,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3308
3352
  }))
3309
3353
  );
3310
3354
  if (!registered.ok) {
3311
- throw new Error(
3312
- `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 }
3313
3358
  );
3314
3359
  }
3315
3360
  const batchError = registered.data.recoveredFromBatchError;
@@ -3330,7 +3375,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3330
3375
  { assumeUnregistered: true }
3331
3376
  );
3332
3377
  if (!registered.ok) {
3333
- 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 });
3334
3379
  }
3335
3380
  }
3336
3381
  if (step.kind === "encryption-network-create") {
@@ -3348,8 +3393,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3348
3393
  ]
3349
3394
  });
3350
3395
  if (!migrated.ok) {
3351
- throw new Error(
3352
- `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 }
3353
3399
  );
3354
3400
  }
3355
3401
  }
@@ -3616,6 +3662,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3616
3662
  );
3617
3663
  return;
3618
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
+ }
3619
3669
  lastError = error;
3620
3670
  if (attempt < delays.length - 1) {
3621
3671
  await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
@@ -3690,14 +3740,14 @@ var _TinyCloudNode = class _TinyCloudNode {
3690
3740
  }
3691
3741
  if (!activation.success && activation.status !== 404) {
3692
3742
  throw Object.assign(
3693
- new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure(activation)}`),
3743
+ new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure2(activation)}`),
3694
3744
  { cause: activation }
3695
3745
  );
3696
3746
  }
3697
3747
  const created = await this.auth.hostOwnedSpaceResult(spaceId);
3698
3748
  if (!created.success) {
3699
3749
  throw Object.assign(
3700
- new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure(created)}`),
3750
+ new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
3701
3751
  { cause: created }
3702
3752
  );
3703
3753
  }
@@ -3706,7 +3756,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3706
3756
  if (!retry.success || retry.skipped?.includes(spaceId)) {
3707
3757
  throw Object.assign(
3708
3758
  new Error(
3709
- `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure(retry)}`
3759
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure2(retry)}`
3710
3760
  ),
3711
3761
  { cause: retry }
3712
3762
  );
@@ -3743,19 +3793,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3743
3793
  if (!host) {
3744
3794
  throw new Error("Owned space hosting requires a TinyCloud host");
3745
3795
  }
3746
- const hosted = await this.auth.hostOwnedSpace(
3747
- spaceId
3748
- );
3749
- if (!hosted) {
3750
- 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
+ );
3751
3802
  }
3752
3803
  const activation = await (0, import_sdk_core8.activateSessionWithHost)(
3753
3804
  host,
3754
3805
  session.delegationHeader
3755
3806
  );
3756
3807
  if (!activation.success || activation.skipped?.includes(spaceId)) {
3757
- throw new Error(
3758
- `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 }
3759
3813
  );
3760
3814
  }
3761
3815
  try {
@@ -3951,23 +4005,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3951
4005
  throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3952
4006
  }
3953
4007
  })();
3954
- const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
3955
- if (!Array.isArray(exactRecap) || !exactRecap.every(
3956
- (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(
3957
- (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
3958
- )
3959
- )) {
3960
- throw new UnsupportedSessionRestoreError(
3961
- "it cannot reconstruct caveat-preserving persisted ReCap authority"
3962
- );
3963
- }
3964
- stagedRecap = exactRecap.map((entry) => ({
3965
- service: entry.service,
3966
- space: entry.space,
3967
- path: entry.path,
3968
- actions: [...entry.actions],
3969
- caveats: cloneRecapCaveats(entry.caveats)
3970
- }));
4008
+ stagedRecap = exactVerifiedRecap(verified);
3971
4009
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3972
4010
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3973
4011
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
@@ -4771,7 +4809,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4771
4809
  if (cached) return cached;
4772
4810
  const response = await fetch(`${this.config.host}/info`);
4773
4811
  if (!response.ok) {
4774
- 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");
4775
4813
  }
4776
4814
  const info = await response.json();
4777
4815
  if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
@@ -4821,9 +4859,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4821
4859
  );
4822
4860
  if (response.status === 404) return null;
4823
4861
  if (!response.ok) {
4824
- throw new Error(
4825
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
4826
- );
4862
+ throw await (0, import_sdk_core8.httpResponseError)(response, `Failed to fetch encryption network ${networkId}`);
4827
4863
  }
4828
4864
  const body = await response.json();
4829
4865
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
@@ -5324,7 +5360,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5324
5360
  headers: { accept: "application/json", "content-type": "application/json", authorization },
5325
5361
  body: (0, import_sdk_core8.canonicalizeEncryptionJson)(request)
5326
5362
  });
5327
- 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");
5328
5364
  const verified = (0, import_sdk_core8.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
5329
5365
  request,
5330
5366
  senderKeyDid: this.credentialHolderDid,
@@ -5546,9 +5582,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5546
5582
  );
5547
5583
  }
5548
5584
  if (!response.ok) {
5549
- throw new Error(
5550
- `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
5551
- );
5585
+ throw await (0, import_sdk_core8.httpResponseError)(response, `Failed to create encryption network ${networkId}`);
5552
5586
  }
5553
5587
  const created = await response.json();
5554
5588
  return created.descriptor;
@@ -5832,6 +5866,42 @@ var _TinyCloudNode = class _TinyCloudNode {
5832
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"))
5833
5867
  );
5834
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
+ }
5835
5905
  /**
5836
5906
  * Install a portable runtime permission delegation into this SDK instance so
5837
5907
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -5856,8 +5926,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5856
5926
  delegation.delegationHeader
5857
5927
  );
5858
5928
  if (!activateResult.success) {
5859
- throw new Error(
5860
- `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 }
5861
5932
  );
5862
5933
  }
5863
5934
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
@@ -5953,8 +6024,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5953
6024
  delegatedSession.delegationHeader
5954
6025
  );
5955
6026
  if (!activateResult.success) {
5956
- throw new Error(
5957
- `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 }
5958
6030
  );
5959
6031
  }
5960
6032
  const delegation = this.runtimeDelegationFromSession(
@@ -5962,7 +6034,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5962
6034
  delegatedEntries,
5963
6035
  spaceId,
5964
6036
  session,
5965
- expiresAt
6037
+ expiresAt,
6038
+ { siwe: prepared.siwe, signature }
5966
6039
  );
5967
6040
  this.runtimePermissionGrants.push({
5968
6041
  session: {
@@ -6139,7 +6212,10 @@ var _TinyCloudNode = class _TinyCloudNode {
6139
6212
  delegationSession.delegationHeader
6140
6213
  );
6141
6214
  if (!activateResult.success) {
6142
- 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
+ );
6143
6219
  }
6144
6220
  if (this._capabilityRegistry && this.session) {
6145
6221
  const sessionKey = {
@@ -6492,8 +6568,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6492
6568
  delegationHeader
6493
6569
  );
6494
6570
  if (!activateResult.success) {
6495
- throw new Error(
6496
- `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 }
6497
6574
  );
6498
6575
  }
6499
6576
  return {
@@ -6529,8 +6606,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6529
6606
  delegationHeader
6530
6607
  );
6531
6608
  if (!activateResult.success) {
6532
- throw new Error(
6533
- `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 }
6534
6612
  );
6535
6613
  }
6536
6614
  return {
@@ -6743,7 +6821,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6743
6821
  }
6744
6822
  return grants;
6745
6823
  }
6746
- runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
6824
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt, siweProof) {
6747
6825
  const resources = this.delegatedResourcesForEntries(entries, spaceId);
6748
6826
  const primary = resources[0];
6749
6827
  return {
@@ -6758,7 +6836,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6758
6836
  delegateDID: session.verificationMethod,
6759
6837
  ownerAddress: session.address,
6760
6838
  chainId: session.chainId,
6761
- host: this.config.host
6839
+ host: this.config.host,
6840
+ siweProof
6762
6841
  };
6763
6842
  }
6764
6843
  runtimeGrantFromDelegation(delegation, session) {
@@ -6951,6 +7030,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6951
7030
  if (grantedAction === requestedAction) {
6952
7031
  return true;
6953
7032
  }
7033
+ if ((0, import_sdk_core8.isExplicitOnlyAction)(requestedAction)) {
7034
+ return false;
7035
+ }
6954
7036
  if (grantedAction.endsWith("/*")) {
6955
7037
  const prefix = grantedAction.slice(0, -2);
6956
7038
  return requestedAction.startsWith(`${prefix}/`);
@@ -7137,7 +7219,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7137
7219
  delegationSession.delegationHeader
7138
7220
  );
7139
7221
  if (!activateResult.success) {
7140
- 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
+ );
7141
7226
  }
7142
7227
  const result = {
7143
7228
  cid: delegationSession.delegationCid,
@@ -7287,7 +7372,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7287
7372
  invokerSession.delegationHeader
7288
7373
  );
7289
7374
  if (!activateResult.success) {
7290
- 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
+ );
7291
7379
  }
7292
7380
  const session = {
7293
7381
  address: mySession.address,
@@ -7404,7 +7492,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7404
7492
  subDelegationSession.delegationHeader
7405
7493
  );
7406
7494
  if (!activateResult.success) {
7407
- 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
+ );
7408
7499
  }
7409
7500
  return {
7410
7501
  cid: subDelegationSession.delegationCid,
@@ -7445,6 +7536,16 @@ var import_sdk_core12 = require("@tinycloud/sdk-core");
7445
7536
 
7446
7537
  // src/delegation.ts
7447
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
+ }
7448
7549
  var SERVICE_BY_SHORT_NAME = {
7449
7550
  kv: "tinycloud.kv",
7450
7551
  sql: "tinycloud.sql",
@@ -7591,6 +7692,15 @@ function signedAuthorityFromCompactUcan(authorization) {
7591
7692
  abilities
7592
7693
  ));
7593
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) {
7594
7704
  if (permissions.length === 0) {
7595
7705
  throw new Error("Validated runtime delegation has no signed capabilities.");
7596
7706
  }
@@ -7602,15 +7712,84 @@ function signedAuthorityFromCompactUcan(authorization) {
7602
7712
  actions: [...permission.actions],
7603
7713
  ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
7604
7714
  }));
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
- };
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()]);
7614
7793
  }
7615
7794
  function canonicalResourcesFromPortableDelegation(delegation) {
7616
7795
  if (delegation.resources !== void 0 && delegation.resources.length > 0) {
@@ -7646,46 +7825,58 @@ function resourcesMatch(actual, expected) {
7646
7825
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
7647
7826
  const host = normalizedHost(options.host);
7648
7827
  if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
7649
- throw new Error(
7828
+ reject(
7829
+ "host_mismatch",
7650
7830
  `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
7651
7831
  );
7652
7832
  }
7653
7833
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
7654
- throw new Error("Runtime delegation has an invalid expiry.");
7834
+ reject("authority_invalid", "Runtime delegation has an invalid expiry.");
7655
7835
  }
7656
7836
  if (delegation.expiry.getTime() <= Date.now()) {
7657
- throw new Error("Runtime delegation is expired.");
7837
+ reject("expired", "Runtime delegation is expired.");
7658
7838
  }
7659
7839
  const authorization = delegation.delegationHeader?.Authorization;
7660
7840
  if (typeof authorization !== "string" || authorization.length === 0) {
7661
- throw new Error("Runtime delegation is missing authorization bytes.");
7841
+ reject("authority_invalid", "Runtime delegation is missing authorization bytes.");
7662
7842
  }
7663
7843
  const cid = node.computeDelegationCid(authorization);
7664
7844
  if (cid !== delegation.cid) {
7665
- 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));
7666
7854
  }
7667
- const signed = signedAuthorityFromCompactUcan(authorization);
7668
7855
  if (signed.expiry.getTime() <= Date.now()) {
7669
- throw new Error("Runtime delegation is expired.");
7856
+ reject("expired", "Runtime delegation is expired.");
7670
7857
  }
7671
7858
  if (delegation.expiry.getTime() !== signed.expiry.getTime()) {
7672
- throw new Error("Runtime delegation expiry does not match signed authority.");
7859
+ reject("authority_invalid", "Runtime delegation expiry does not match signed authority.");
7673
7860
  }
7674
7861
  if (!didPrincipalsMatch(signed.audience, node.sessionDid)) {
7675
- throw new Error(
7862
+ reject(
7863
+ "audience_mismatch",
7676
7864
  `Runtime delegation targets ${signed.audience} but this session is ${node.sessionDid}.`
7677
7865
  );
7678
7866
  }
7679
7867
  if (!didPrincipalsMatch(delegation.delegateDID, signed.audience)) {
7680
- 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.");
7681
7872
  }
7682
- const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
7683
- if (!resourcesMatch(declaredResources, signed.resources)) {
7684
- 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.");
7685
7875
  }
7686
7876
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7687
7877
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
7688
- throw new Error(
7878
+ reject(
7879
+ "cid_mismatch",
7689
7880
  "A different authorization is already installed for this runtime delegation CID."
7690
7881
  );
7691
7882
  }
@@ -7715,22 +7906,28 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
7715
7906
  })),
7716
7907
  expiry: new Date(signed.expiry),
7717
7908
  delegateDID: signed.audience,
7718
- host
7909
+ host,
7910
+ ...compact ? {} : { siweProof: delegation.siweProof }
7719
7911
  };
7720
- if (node.isSessionOnly === true && typeof node.did === "string" && (0, import_sdk_core9.principalDidEquals)(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7721
- await node.useDelegation(installedCandidate);
7722
- } else {
7723
- 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));
7724
7920
  }
7725
7921
  const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7726
7922
  if (!installed) {
7727
- 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.");
7728
7924
  }
7729
7925
  if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed.audience) || installed.expiry.getTime() !== signed.expiry.getTime() || !resourcesMatch(
7730
7926
  canonicalResourcesFromPortableDelegation(installed),
7731
7927
  signed.resources
7732
7928
  ) || normalizedHost(installed.host ?? "") !== host) {
7733
- throw new Error(
7929
+ reject(
7930
+ "activation_rejected",
7734
7931
  "Runtime delegation activation installed authority that differs from the validated authority."
7735
7932
  );
7736
7933
  }