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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -262,6 +262,10 @@ var defaultSignStrategy = { type: "auto-sign" };
262
262
  // src/authorization/NodeUserAuthorization.ts
263
263
  var DECRYPT_ACTION = ENCRYPTION.DECRYPT;
264
264
  var NETWORK_CREATE_ACTION = ENCRYPTION.NETWORK_CREATE;
265
+ function describeHostFailure(result) {
266
+ const text = result.error?.trim();
267
+ return text ? `${result.status} - ${text}` : `${result.status}`;
268
+ }
265
269
  function canonicalizeOpenKeyManifestJson(value) {
266
270
  const json = JSON.stringify(value);
267
271
  if (json === void 0) {
@@ -790,10 +794,12 @@ var NodeUserAuthorization = class {
790
794
  return;
791
795
  }
792
796
  try {
793
- const created = (await this.hostSpace()).success;
794
- if (!created) {
795
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
796
- handler.onSpaceCreationFailed?.(creationContext, err);
797
+ const created = await this.hostSpace();
798
+ if (!created.success) {
799
+ const err = Object.assign(
800
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
801
+ { cause: created }
802
+ );
797
803
  throw err;
798
804
  }
799
805
  } catch (error) {
@@ -809,8 +815,9 @@ var NodeUserAuthorization = class {
809
815
  this._tinyCloudSession.delegationHeader
810
816
  );
811
817
  if (!retryResult.success) {
812
- const err = new Error(
813
- `Failed to activate session after creating space: ${retryResult.error}`
818
+ const err = Object.assign(
819
+ new Error(`Failed to activate session after creating space: ${describeHostFailure(retryResult)}`),
820
+ { cause: retryResult }
814
821
  );
815
822
  handler.onSpaceCreationFailed?.(creationContext, err);
816
823
  throw err;
@@ -827,10 +834,12 @@ var NodeUserAuthorization = class {
827
834
  return;
828
835
  }
829
836
  try {
830
- const created = (await this.hostSpace()).success;
831
- if (!created) {
832
- const err = new Error(`Failed to create space: ${primarySpaceId}`);
833
- handler.onSpaceCreationFailed?.(creationContext, err);
837
+ const created = await this.hostSpace();
838
+ if (!created.success) {
839
+ const err = Object.assign(
840
+ new Error(`Failed to create space ${primarySpaceId}: ${describeHostFailure(created)}`),
841
+ { cause: created }
842
+ );
834
843
  throw err;
835
844
  }
836
845
  } catch (error) {
@@ -846,8 +855,9 @@ var NodeUserAuthorization = class {
846
855
  this._tinyCloudSession.delegationHeader
847
856
  );
848
857
  if (!retryResult.success) {
849
- const err = new Error(
850
- `Failed to activate session after creating space: ${retryResult.error}`
858
+ const err = Object.assign(
859
+ new Error(`Failed to activate session after creating space: ${describeHostFailure(retryResult)}`),
860
+ { cause: retryResult }
851
861
  );
852
862
  handler.onSpaceCreationFailed?.(creationContext, err);
853
863
  throw err;
@@ -855,7 +865,10 @@ var NodeUserAuthorization = class {
855
865
  handler.onSpaceCreated?.(creationContext);
856
866
  return;
857
867
  }
858
- throw new Error(`Failed to activate session: ${result.error}`);
868
+ throw Object.assign(
869
+ new Error(`Failed to activate session: ${describeHostFailure(result)}`),
870
+ { cause: result }
871
+ );
859
872
  }
860
873
  recordActivationSkippedSpaces(result, primarySpaceId) {
861
874
  if (result.success) {
@@ -1645,14 +1658,14 @@ var NodeUserAuthorization = class {
1645
1658
  * Request signature via event emitter with timeout.
1646
1659
  */
1647
1660
  requestSignatureViaEmitter(request, emitter, timeout) {
1648
- return new Promise((resolve, reject) => {
1661
+ return new Promise((resolve, reject2) => {
1649
1662
  const timeoutId = setTimeout(() => {
1650
- reject(new Error("Sign request timed out"));
1663
+ reject2(new Error("Sign request timed out"));
1651
1664
  }, timeout);
1652
1665
  const respond = async (response) => {
1653
1666
  clearTimeout(timeoutId);
1654
1667
  if (!response.approved) {
1655
- reject(
1668
+ reject2(
1656
1669
  new Error(response.reason ?? "Sign request rejected via emitter")
1657
1670
  );
1658
1671
  } else {
@@ -1670,6 +1683,7 @@ import {
1670
1683
  TinyCloud,
1671
1684
  activateSessionWithHost as activateSessionWithHost2,
1672
1685
  authorizationVerdictOf,
1686
+ httpResponseError,
1673
1687
  KVService as KVService2,
1674
1688
  SQLService as SQLService2,
1675
1689
  DuckDbService as DuckDbService2,
@@ -1703,6 +1717,7 @@ import {
1703
1717
  recapCaveatsEqual,
1704
1718
  expandPermissionEntries as expandPermissionEntriesCore,
1705
1719
  isCapabilitySubset,
1720
+ isExplicitOnlyAction,
1706
1721
  parseRecapCapabilities,
1707
1722
  SERVICE_LONG_TO_SHORT,
1708
1723
  SERVICE_SHORT_TO_LONG,
@@ -1725,7 +1740,10 @@ import {
1725
1740
  CREDENTIAL_INVITATION_REQUEST_DOMAIN,
1726
1741
  validateShareDeliveryAuthorizationV3Bytes,
1727
1742
  verifyEip191MessageSignature,
1728
- signCompactUcanRootAuthorization
1743
+ signCompactUcanRootAuthorization,
1744
+ isStorageFullError,
1745
+ STORAGE_FULL_MESSAGE,
1746
+ STORAGE_WRITE_TOO_LARGE_MESSAGE
1729
1747
  } from "@tinycloud/sdk-core";
1730
1748
  import {
1731
1749
  parsePermissionHint
@@ -2313,6 +2331,25 @@ function jsonFromVerifier(value) {
2313
2331
  function verifierRecapEntries(entries) {
2314
2332
  return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
2315
2333
  }
2334
+ function exactVerifiedRecap(verified) {
2335
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
2336
+ if (!Array.isArray(exactRecap) || !exactRecap.every(
2337
+ (entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
2338
+ (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
2339
+ )
2340
+ )) {
2341
+ throw new UnsupportedSessionRestoreError(
2342
+ "it cannot reconstruct caveat-preserving persisted ReCap authority"
2343
+ );
2344
+ }
2345
+ return exactRecap.map((entry) => ({
2346
+ service: entry.service,
2347
+ space: entry.space,
2348
+ path: entry.path,
2349
+ actions: [...entry.actions],
2350
+ caveats: cloneRecapCaveats(entry.caveats)
2351
+ }));
2352
+ }
2316
2353
  var ServiceGraphLifetime = class {
2317
2354
  constructor(invokeFn, invokeAnyFn, fetchFn) {
2318
2355
  this.invokeFn = invokeFn;
@@ -2408,7 +2445,7 @@ function persistedExpiry(value) {
2408
2445
  function sameInstant(left, right) {
2409
2446
  return left.getTime() === right.getTime();
2410
2447
  }
2411
- function describeHostFailure(result) {
2448
+ function describeHostFailure2(result) {
2412
2449
  const text = result.error?.trim();
2413
2450
  return text ? `${result.status} - ${text}` : `${result.status}`;
2414
2451
  }
@@ -3154,12 +3191,13 @@ var _TinyCloudNode = class _TinyCloudNode {
3154
3191
  if (!auth) {
3155
3192
  throw new Error("Account bootstrap requires an active wallet session");
3156
3193
  }
3194
+ const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
3157
3195
  const skipped = auth.lastActivationSkippedSpaceIds;
3196
+ if (skipped.includes(accountSpaceId)) return { action: "run", mode: "fresh" };
3158
3197
  if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
3159
- return { action: "run", mode: "fresh" };
3198
+ return { action: "run", mode: "repair" };
3160
3199
  }
3161
3200
  try {
3162
- const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
3163
3201
  const markerPermission = {
3164
3202
  service: "tinycloud.kv",
3165
3203
  space: accountSpaceId,
@@ -3171,7 +3209,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3171
3209
  }
3172
3210
  const marker = await this.readBootstrapCompletionMarker();
3173
3211
  if (marker.ok) {
3174
- if (this.isAcceptedBootstrapCompletionMarker(marker.data.data)) {
3212
+ if (this.isAcceptedBootstrapCompletionMarker(marker.data.data, steps.map((step) => step.id))) {
3175
3213
  return { action: "skip" };
3176
3214
  }
3177
3215
  return { action: "run", mode: "repair" };
@@ -3215,15 +3253,22 @@ var _TinyCloudNode = class _TinyCloudNode {
3215
3253
  marker
3216
3254
  );
3217
3255
  if (!written.ok) {
3218
- throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
3256
+ throw Object.assign(
3257
+ new Error(`Failed to write bootstrap completion marker: ${written.error.message}`),
3258
+ { cause: written.error }
3259
+ );
3219
3260
  }
3220
3261
  }
3221
- isAcceptedBootstrapCompletionMarker(value) {
3262
+ /**
3263
+ * A marker written before a canonical space was added triggers one bounded repair.
3264
+ */
3265
+ isAcceptedBootstrapCompletionMarker(value, canonicalStepIds) {
3222
3266
  if (typeof value !== "object" || value === null) return false;
3223
3267
  const entries = Object.entries(value);
3224
3268
  const markerValue = (name) => entries.find(([key]) => key === name)?.[1];
3225
3269
  const version = markerValue("v");
3226
- return typeof version === "number" && Number.isInteger(version) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version);
3270
+ const stepIds = markerValue("stepIds");
3271
+ return typeof version === "number" && Number.isInteger(version) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version) && Array.isArray(stepIds) && canonicalStepIds.every((stepId) => stepIds.includes(stepId));
3227
3272
  }
3228
3273
  markerReadIsUnhostedSpace(meta) {
3229
3274
  return meta?.status === 404;
@@ -3261,17 +3306,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3261
3306
  rawAbilities
3262
3307
  });
3263
3308
  } catch (err) {
3264
- throw new Error(
3265
- `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3309
+ throw Object.assign(
3310
+ new Error(
3311
+ `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3312
+ ),
3313
+ { cause: err }
3266
3314
  );
3267
3315
  }
3268
3316
  sessions.set(step.space, session);
3269
3317
  }
3270
3318
  for (const step of steps) {
3271
3319
  if (step.kind !== "host") continue;
3272
- const hosted = await auth.hostOwnedSpace(step.spaceId, "bootstrap-host");
3273
- if (!hosted) {
3274
- throw new Error(`Failed to host bootstrap space: ${step.spaceId}`);
3320
+ const hosted = await auth.hostOwnedSpaceResult(step.spaceId, "bootstrap-host");
3321
+ if (!hosted.success) {
3322
+ throw Object.assign(
3323
+ new Error(`Failed to host bootstrap space ${step.spaceId}: ${describeHostFailure2(hosted)}`),
3324
+ { cause: hosted }
3325
+ );
3275
3326
  }
3276
3327
  }
3277
3328
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -3283,8 +3334,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3283
3334
  }
3284
3335
  const activated = await activateSessionWithHost2(host, session.delegationHeader);
3285
3336
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
3286
- throw new Error(
3287
- `Failed to activate bootstrap session for ${step.spaceId}: ${activated.error ?? "space was skipped"}`
3337
+ throw Object.assign(
3338
+ new Error(
3339
+ `Failed to activate bootstrap session for ${step.spaceId}: ${activated.success ? "space was skipped" : describeHostFailure2(activated)}`
3340
+ ),
3341
+ { cause: activated }
3288
3342
  );
3289
3343
  }
3290
3344
  this.registerBootstrapRuntimeGrant(
@@ -3297,33 +3351,43 @@ var _TinyCloudNode = class _TinyCloudNode {
3297
3351
  if (step.kind === "account-index-schema") {
3298
3352
  const ensured = await this.account.index.ensure();
3299
3353
  if (!ensured.ok) {
3300
- throw new Error(`Failed to create account index schema: ${ensured.error.message}`);
3354
+ throw Object.assign(new Error(`Failed to create account index schema: ${ensured.error.message}`), { cause: ensured.error });
3301
3355
  }
3302
3356
  }
3303
3357
  if (step.kind === "seed-spaces") {
3304
- const registered = await this.account.spaces.registerBatch(
3305
- step.spaces.map((space) => ({
3306
- spaceId: space.spaceId,
3307
- name: space.name,
3308
- ownerDid: this.did,
3309
- type: "owned",
3310
- permissions: ["*"],
3311
- status: "active"
3312
- }))
3313
- );
3314
- if (!registered.ok) {
3315
- throw new Error(
3316
- `Failed to seed account spaces: ${registered.error.message}`
3317
- );
3318
- }
3319
- const batchError = registered.data.recoveredFromBatchError;
3320
- if (batchError) {
3321
- warnings.push({
3322
- stepId: step.id,
3323
- kind: "batch-write-reconciled",
3324
- code: batchError.code,
3325
- message: batchError.message
3326
- });
3358
+ const inputs = step.spaces.map((space) => ({
3359
+ spaceId: space.spaceId,
3360
+ name: space.name,
3361
+ ownerDid: this.did,
3362
+ type: "owned",
3363
+ permissions: ["*"],
3364
+ status: "active"
3365
+ }));
3366
+ if (mode === "fresh") {
3367
+ const registered = await this.account.spaces.registerBatch(inputs);
3368
+ if (!registered.ok) {
3369
+ throw Object.assign(
3370
+ new Error(`Failed to seed account spaces: ${registered.error.message}`),
3371
+ { cause: registered.error }
3372
+ );
3373
+ }
3374
+ const batchError = registered.data.recoveredFromBatchError;
3375
+ if (batchError) {
3376
+ warnings.push({
3377
+ stepId: step.id,
3378
+ kind: "batch-write-reconciled",
3379
+ code: batchError.code,
3380
+ message: batchError.message
3381
+ });
3382
+ }
3383
+ } else {
3384
+ const ensured = await this.account.spaces.registerMissing(inputs);
3385
+ if (!ensured.ok) {
3386
+ throw Object.assign(
3387
+ new Error(`Failed to seed account spaces: ${ensured.error.message}`),
3388
+ { cause: ensured.error }
3389
+ );
3390
+ }
3327
3391
  }
3328
3392
  }
3329
3393
  if (step.kind === "seed-applications") {
@@ -3334,7 +3398,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3334
3398
  { assumeUnregistered: true }
3335
3399
  );
3336
3400
  if (!registered.ok) {
3337
- throw new Error(`Failed to seed bootstrap applications: ${registered.error.message}`);
3401
+ throw Object.assign(new Error(`Failed to seed bootstrap applications: ${registered.error.message}`), { cause: registered.error });
3338
3402
  }
3339
3403
  }
3340
3404
  if (step.kind === "encryption-network-create") {
@@ -3352,8 +3416,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3352
3416
  ]
3353
3417
  });
3354
3418
  if (!migrated.ok) {
3355
- throw new Error(
3356
- `Failed to create secret_records schema: ${migrated.error.message}`
3419
+ throw Object.assign(
3420
+ new Error(`Failed to create secret_records schema: ${migrated.error.message}`),
3421
+ { cause: migrated.error }
3357
3422
  );
3358
3423
  }
3359
3424
  }
@@ -3620,6 +3685,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3620
3685
  );
3621
3686
  return;
3622
3687
  }
3688
+ if (isStorageFullError(error) || isStorageFullError(error?.cause) || message.includes(STORAGE_FULL_MESSAGE) || message.includes(STORAGE_WRITE_TOO_LARGE_MESSAGE)) {
3689
+ console.warn("TinyCloud account registry sync stopped: storage is full", error);
3690
+ return;
3691
+ }
3623
3692
  lastError = error;
3624
3693
  if (attempt < delays.length - 1) {
3625
3694
  await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
@@ -3694,14 +3763,14 @@ var _TinyCloudNode = class _TinyCloudNode {
3694
3763
  }
3695
3764
  if (!activation.success && activation.status !== 404) {
3696
3765
  throw Object.assign(
3697
- new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure(activation)}`),
3766
+ new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure2(activation)}`),
3698
3767
  { cause: activation }
3699
3768
  );
3700
3769
  }
3701
3770
  const created = await this.auth.hostOwnedSpaceResult(spaceId);
3702
3771
  if (!created.success) {
3703
3772
  throw Object.assign(
3704
- new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure(created)}`),
3773
+ new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
3705
3774
  { cause: created }
3706
3775
  );
3707
3776
  }
@@ -3710,7 +3779,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3710
3779
  if (!retry.success || retry.skipped?.includes(spaceId)) {
3711
3780
  throw Object.assign(
3712
3781
  new Error(
3713
- `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure(retry)}`
3782
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure2(retry)}`
3714
3783
  ),
3715
3784
  { cause: retry }
3716
3785
  );
@@ -3747,19 +3816,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3747
3816
  if (!host) {
3748
3817
  throw new Error("Owned space hosting requires a TinyCloud host");
3749
3818
  }
3750
- const hosted = await this.auth.hostOwnedSpace(
3751
- spaceId
3752
- );
3753
- if (!hosted) {
3754
- throw new Error(`Failed to host owned space: ${spaceId}`);
3819
+ const hosted = await this.auth.hostOwnedSpaceResult(spaceId);
3820
+ if (!hosted.success) {
3821
+ throw Object.assign(
3822
+ new Error(`Failed to host owned space ${spaceId}: ${describeHostFailure2(hosted)}`),
3823
+ { cause: hosted }
3824
+ );
3755
3825
  }
3756
3826
  const activation = await activateSessionWithHost2(
3757
3827
  host,
3758
3828
  session.delegationHeader
3759
3829
  );
3760
3830
  if (!activation.success || activation.skipped?.includes(spaceId)) {
3761
- throw new Error(
3762
- `Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
3831
+ throw Object.assign(
3832
+ new Error(
3833
+ `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
3834
+ ),
3835
+ { cause: activation }
3763
3836
  );
3764
3837
  }
3765
3838
  try {
@@ -3955,23 +4028,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3955
4028
  throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3956
4029
  }
3957
4030
  })();
3958
- const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
3959
- if (!Array.isArray(exactRecap) || !exactRecap.every(
3960
- (entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
3961
- (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
3962
- )
3963
- )) {
3964
- throw new UnsupportedSessionRestoreError(
3965
- "it cannot reconstruct caveat-preserving persisted ReCap authority"
3966
- );
3967
- }
3968
- stagedRecap = exactRecap.map((entry) => ({
3969
- service: entry.service,
3970
- space: entry.space,
3971
- path: entry.path,
3972
- actions: [...entry.actions],
3973
- caveats: cloneRecapCaveats(entry.caveats)
3974
- }));
4031
+ stagedRecap = exactVerifiedRecap(verified);
3975
4032
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3976
4033
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3977
4034
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
@@ -4775,7 +4832,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4775
4832
  if (cached) return cached;
4776
4833
  const response = await fetch(`${this.config.host}/info`);
4777
4834
  if (!response.ok) {
4778
- throw new Error(`Failed to fetch node info: HTTP ${response.status}`);
4835
+ throw await httpResponseError(response, "Failed to fetch node info");
4779
4836
  }
4780
4837
  const info = await response.json();
4781
4838
  if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
@@ -4825,9 +4882,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4825
4882
  );
4826
4883
  if (response.status === 404) return null;
4827
4884
  if (!response.ok) {
4828
- throw new Error(
4829
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
4830
- );
4885
+ throw await httpResponseError(response, `Failed to fetch encryption network ${networkId}`);
4831
4886
  }
4832
4887
  const body = await response.json();
4833
4888
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
@@ -5328,7 +5383,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5328
5383
  headers: { accept: "application/json", "content-type": "application/json", authorization },
5329
5384
  body: canonicalizeEncryptionJson(request)
5330
5385
  });
5331
- if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
5386
+ if (!response.ok) throw await httpResponseError(response, "V3 share delivery authorization failed");
5332
5387
  const verified = validateShareDeliveryAuthorizationV3Bytes(new Uint8Array(await response.arrayBuffer()), {
5333
5388
  request,
5334
5389
  senderKeyDid: this.credentialHolderDid,
@@ -5550,9 +5605,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5550
5605
  );
5551
5606
  }
5552
5607
  if (!response.ok) {
5553
- throw new Error(
5554
- `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
5555
- );
5608
+ throw await httpResponseError(response, `Failed to create encryption network ${networkId}`);
5556
5609
  }
5557
5610
  const created = await response.json();
5558
5611
  return created.descriptor;
@@ -5836,6 +5889,42 @@ var _TinyCloudNode = class _TinyCloudNode {
5836
5889
  (left, right) => left.service.localeCompare(right.service) || (left.space ?? "").localeCompare(right.space ?? "") || left.path.localeCompare(right.path) || left.actions.join("\0").localeCompare(right.actions.join("\0"))
5837
5890
  );
5838
5891
  }
5892
+ /**
5893
+ * Verify a wallet-signed session grant (a CACAO built from a SIWE and its
5894
+ * EIP-191 signature) issued to this instance's live session key. The
5895
+ * verifier rebuilds the CACAO from `siwe` and `signature`, so the grant is
5896
+ * accepted only when its authorization bytes and CID are exactly that CACAO,
5897
+ * its signer is `address`, its audience is this session key, it is valid
5898
+ * now, and its ReCap covers `spaceId` or names only raw encryption networks
5899
+ * (a decrypt-only grant has no space). Returns the signed ReCap and expiry.
5900
+ * Bindings without `validateSessionGrant` fall back to the session verifier,
5901
+ * which also refuses decrypt-only grants.
5902
+ */
5903
+ verifySessionGrant(grant) {
5904
+ const bindings = this.wasmBindings;
5905
+ const validate = typeof bindings.validateSessionGrant === "function" ? bindings.validateSessionGrant.bind(bindings) : bindings.validatePersistedSession?.bind(bindings);
5906
+ if (validate === void 0) {
5907
+ throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
5908
+ }
5909
+ const verified = validate({
5910
+ delegationHeader: { Authorization: grant.delegationHeader.Authorization },
5911
+ delegationCid: grant.delegationCid,
5912
+ spaceId: grant.spaceId,
5913
+ jwk: this.sessionKeyJwk,
5914
+ address: canonicalizeAddress2(grant.address),
5915
+ chainId: grant.chainId,
5916
+ siwe: grant.siwe,
5917
+ signature: grant.signature
5918
+ });
5919
+ if (typeof verified.expiresAt !== "string") {
5920
+ throw new Error("Session grant has no signed expiry.");
5921
+ }
5922
+ const expiresAt = new Date(verified.expiresAt);
5923
+ if (!Number.isFinite(expiresAt.getTime())) {
5924
+ throw new Error("Session grant has an invalid signed expiry.");
5925
+ }
5926
+ return { recap: exactVerifiedRecap(verified), expiresAt };
5927
+ }
5839
5928
  /**
5840
5929
  * Install a portable runtime permission delegation into this SDK instance so
5841
5930
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -5860,8 +5949,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5860
5949
  delegation.delegationHeader
5861
5950
  );
5862
5951
  if (!activateResult.success) {
5863
- throw new Error(
5864
- `Failed to activate runtime permission delegation: ${activateResult.error}`
5952
+ throw Object.assign(
5953
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
5954
+ { cause: activateResult }
5865
5955
  );
5866
5956
  }
5867
5957
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
@@ -5957,8 +6047,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5957
6047
  delegatedSession.delegationHeader
5958
6048
  );
5959
6049
  if (!activateResult.success) {
5960
- throw new Error(
5961
- `Failed to activate runtime permission delegation: ${activateResult.error}`
6050
+ throw Object.assign(
6051
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
6052
+ { cause: activateResult }
5962
6053
  );
5963
6054
  }
5964
6055
  const delegation = this.runtimeDelegationFromSession(
@@ -5966,7 +6057,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5966
6057
  delegatedEntries,
5967
6058
  spaceId,
5968
6059
  session,
5969
- expiresAt
6060
+ expiresAt,
6061
+ { siwe: prepared.siwe, signature }
5970
6062
  );
5971
6063
  this.runtimePermissionGrants.push({
5972
6064
  session: {
@@ -6143,7 +6235,10 @@ var _TinyCloudNode = class _TinyCloudNode {
6143
6235
  delegationSession.delegationHeader
6144
6236
  );
6145
6237
  if (!activateResult.success) {
6146
- throw new Error(`Failed to activate public space delegation: ${activateResult.error}`);
6238
+ throw Object.assign(
6239
+ new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
6240
+ { cause: activateResult }
6241
+ );
6147
6242
  }
6148
6243
  if (this._capabilityRegistry && this.session) {
6149
6244
  const sessionKey = {
@@ -6496,8 +6591,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6496
6591
  delegationHeader
6497
6592
  );
6498
6593
  if (!activateResult.success) {
6499
- throw new Error(
6500
- `Failed to activate delegation with host: ${activateResult.error}`
6594
+ throw Object.assign(
6595
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6596
+ { cause: activateResult }
6501
6597
  );
6502
6598
  }
6503
6599
  return {
@@ -6533,8 +6629,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6533
6629
  delegationHeader
6534
6630
  );
6535
6631
  if (!activateResult.success) {
6536
- throw new Error(
6537
- `Failed to activate delegation with host: ${activateResult.error}`
6632
+ throw Object.assign(
6633
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6634
+ { cause: activateResult }
6538
6635
  );
6539
6636
  }
6540
6637
  return {
@@ -6747,7 +6844,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6747
6844
  }
6748
6845
  return grants;
6749
6846
  }
6750
- runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
6847
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt, siweProof) {
6751
6848
  const resources = this.delegatedResourcesForEntries(entries, spaceId);
6752
6849
  const primary = resources[0];
6753
6850
  return {
@@ -6762,7 +6859,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6762
6859
  delegateDID: session.verificationMethod,
6763
6860
  ownerAddress: session.address,
6764
6861
  chainId: session.chainId,
6765
- host: this.config.host
6862
+ host: this.config.host,
6863
+ siweProof
6766
6864
  };
6767
6865
  }
6768
6866
  runtimeGrantFromDelegation(delegation, session) {
@@ -6955,6 +7053,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6955
7053
  if (grantedAction === requestedAction) {
6956
7054
  return true;
6957
7055
  }
7056
+ if (isExplicitOnlyAction(requestedAction)) {
7057
+ return false;
7058
+ }
6958
7059
  if (grantedAction.endsWith("/*")) {
6959
7060
  const prefix = grantedAction.slice(0, -2);
6960
7061
  return requestedAction.startsWith(`${prefix}/`);
@@ -7141,7 +7242,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7141
7242
  delegationSession.delegationHeader
7142
7243
  );
7143
7244
  if (!activateResult.success) {
7144
- throw new Error(`Failed to activate delegation: ${activateResult.error}`);
7245
+ throw Object.assign(
7246
+ new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
7247
+ { cause: activateResult }
7248
+ );
7145
7249
  }
7146
7250
  const result = {
7147
7251
  cid: delegationSession.delegationCid,
@@ -7291,7 +7395,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7291
7395
  invokerSession.delegationHeader
7292
7396
  );
7293
7397
  if (!activateResult.success) {
7294
- throw new Error(`Failed to activate delegated session: ${activateResult.error}`);
7398
+ throw Object.assign(
7399
+ new Error(`Failed to activate delegated session: ${describeHostFailure2(activateResult)}`),
7400
+ { cause: activateResult }
7401
+ );
7295
7402
  }
7296
7403
  const session = {
7297
7404
  address: mySession.address,
@@ -7408,7 +7515,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7408
7515
  subDelegationSession.delegationHeader
7409
7516
  );
7410
7517
  if (!activateResult.success) {
7411
- throw new Error(`Failed to activate sub-delegation: ${activateResult.error}`);
7518
+ throw Object.assign(
7519
+ new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
7520
+ { cause: activateResult }
7521
+ );
7412
7522
  }
7413
7523
  return {
7414
7524
  cid: subDelegationSession.delegationCid,
@@ -7473,6 +7583,16 @@ import {
7473
7583
  decodeBase64Url,
7474
7584
  principalDidEquals as principalDidEquals3
7475
7585
  } from "@tinycloud/sdk-core";
7586
+ var RuntimeDelegationRejectedError = class extends Error {
7587
+ constructor(reason, message) {
7588
+ super(message);
7589
+ this.name = "RuntimeDelegationRejectedError";
7590
+ this.reason = reason;
7591
+ }
7592
+ };
7593
+ function reject(reason, message) {
7594
+ throw new RuntimeDelegationRejectedError(reason, message);
7595
+ }
7476
7596
  var SERVICE_BY_SHORT_NAME = {
7477
7597
  kv: "tinycloud.kv",
7478
7598
  sql: "tinycloud.sql",
@@ -7619,6 +7739,15 @@ function signedAuthorityFromCompactUcan(authorization) {
7619
7739
  abilities
7620
7740
  ));
7621
7741
  }
7742
+ return canonicalSignedAuthority({
7743
+ issuer: payload.iss,
7744
+ audience: payload.aud,
7745
+ expiry,
7746
+ proofs: [...payload.prf],
7747
+ ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth }
7748
+ }, permissions);
7749
+ }
7750
+ function canonicalSignedAuthority(signed, permissions) {
7622
7751
  if (permissions.length === 0) {
7623
7752
  throw new Error("Validated runtime delegation has no signed capabilities.");
7624
7753
  }
@@ -7630,15 +7759,84 @@ function signedAuthorityFromCompactUcan(authorization) {
7630
7759
  actions: [...permission.actions],
7631
7760
  ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
7632
7761
  }));
7633
- return {
7634
- issuer: payload.iss,
7635
- audience: payload.aud,
7636
- expiry,
7637
- proofs: [...payload.prf],
7638
- ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth },
7639
- permissions: canonicalPermissions,
7640
- resources: canonicalResources
7641
- };
7762
+ return { ...signed, permissions: canonicalPermissions, resources: canonicalResources };
7763
+ }
7764
+ var RAW_ENCRYPTION_PREFIX = "urn:tinycloud:encryption:";
7765
+ var PKH_SPACE = /^tinycloud:pkh:eip155:(\d+):(0x[0-9a-fA-F]{40}):(.+)$/;
7766
+ function comparableSpace(space) {
7767
+ const match = PKH_SPACE.exec(space);
7768
+ return match ? `tinycloud:pkh:eip155:${match[1]}:${match[2].toLowerCase()}:${match[3]}` : space;
7769
+ }
7770
+ function signedAuthorityFromSessionGrant(node, delegation, authorization, cid) {
7771
+ const proof = delegation.siweProof;
7772
+ if (proof === void 0 || proof === null || typeof proof !== "object" || typeof proof.siwe !== "string" || typeof proof.signature !== "string") {
7773
+ reject(
7774
+ "proof_missing",
7775
+ "Runtime delegation is a wallet-signed session grant stored without its signed SIWE proof, so its authority cannot be verified."
7776
+ );
7777
+ }
7778
+ if (typeof node.verifySessionGrant !== "function") {
7779
+ reject("unsupported", "This runtime cannot verify wallet-signed session grants.");
7780
+ }
7781
+ const owner = `did:pkh:eip155:${delegation.chainId}:${delegation.ownerAddress}`;
7782
+ if (typeof node.did !== "string" || !didPrincipalsMatch(owner, node.did)) {
7783
+ reject("owner_mismatch", "Runtime delegation is not signed by this session's owner.");
7784
+ }
7785
+ let verified;
7786
+ try {
7787
+ verified = node.verifySessionGrant({
7788
+ delegationHeader: { Authorization: authorization },
7789
+ delegationCid: cid,
7790
+ spaceId: delegation.spaceId,
7791
+ address: delegation.ownerAddress,
7792
+ chainId: delegation.chainId,
7793
+ siwe: proof.siwe,
7794
+ signature: proof.signature
7795
+ });
7796
+ } catch (error) {
7797
+ reject("proof_invalid", error instanceof Error ? error.message : String(error));
7798
+ }
7799
+ const space = comparableSpace(delegation.spaceId);
7800
+ const spaceOwner = PKH_SPACE.exec(delegation.spaceId);
7801
+ if (!spaceOwner || !didPrincipalsMatch(`did:pkh:eip155:${spaceOwner[1]}:${spaceOwner[2]}`, owner)) {
7802
+ reject("authority_invalid", "Runtime delegation space is not owned by its signer.");
7803
+ }
7804
+ const groups = /* @__PURE__ */ new Map();
7805
+ for (const entry of verified.recap) {
7806
+ const service = entry.service.startsWith("tinycloud.") ? entry.service : `tinycloud.${entry.service}`;
7807
+ const raw = entry.space === "encryption";
7808
+ if (raw) {
7809
+ if (service !== "tinycloud.encryption" || !entry.path.startsWith(RAW_ENCRYPTION_PREFIX)) {
7810
+ reject("authority_invalid", "Runtime delegation has an unsupported signed raw resource.");
7811
+ }
7812
+ const networkOwner = entry.path.slice(RAW_ENCRYPTION_PREFIX.length, entry.path.lastIndexOf(":"));
7813
+ if (!didPrincipalsMatch(networkOwner, owner)) {
7814
+ reject("authority_invalid", "Runtime delegation names an encryption network its signer does not own.");
7815
+ }
7816
+ } else if (service === "tinycloud.encryption" || comparableSpace(entry.space) !== space) {
7817
+ reject("authority_invalid", "Runtime delegation has signed authority outside its space.");
7818
+ }
7819
+ const caveats = signedCaveats(entry.caveats ?? []);
7820
+ const key = JSON.stringify([service, raw ? "" : entry.space, entry.path, canonicalizeRecapCaveats2(caveats)]);
7821
+ const group = groups.get(key) ?? {
7822
+ service,
7823
+ ...raw ? {} : { space: entry.space },
7824
+ path: entry.path,
7825
+ actions: [],
7826
+ ...caveats === void 0 ? {} : { caveats }
7827
+ };
7828
+ for (const action of entry.actions) {
7829
+ if (!group.actions.includes(action)) group.actions.push(action);
7830
+ }
7831
+ groups.set(key, group);
7832
+ }
7833
+ return canonicalSignedAuthority({
7834
+ issuer: owner,
7835
+ // The verifier bound the SIWE audience to this runtime's own session key.
7836
+ audience: node.sessionDid,
7837
+ expiry: verified.expiresAt,
7838
+ proofs: []
7839
+ }, [...groups.values()]);
7642
7840
  }
7643
7841
  function canonicalResourcesFromPortableDelegation(delegation) {
7644
7842
  if (delegation.resources !== void 0 && delegation.resources.length > 0) {
@@ -7674,46 +7872,58 @@ function resourcesMatch(actual, expected) {
7674
7872
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
7675
7873
  const host = normalizedHost(options.host);
7676
7874
  if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
7677
- throw new Error(
7875
+ reject(
7876
+ "host_mismatch",
7678
7877
  `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
7679
7878
  );
7680
7879
  }
7681
7880
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
7682
- throw new Error("Runtime delegation has an invalid expiry.");
7881
+ reject("authority_invalid", "Runtime delegation has an invalid expiry.");
7683
7882
  }
7684
7883
  if (delegation.expiry.getTime() <= Date.now()) {
7685
- throw new Error("Runtime delegation is expired.");
7884
+ reject("expired", "Runtime delegation is expired.");
7686
7885
  }
7687
7886
  const authorization = delegation.delegationHeader?.Authorization;
7688
7887
  if (typeof authorization !== "string" || authorization.length === 0) {
7689
- throw new Error("Runtime delegation is missing authorization bytes.");
7888
+ reject("authority_invalid", "Runtime delegation is missing authorization bytes.");
7690
7889
  }
7691
7890
  const cid = node.computeDelegationCid(authorization);
7692
7891
  if (cid !== delegation.cid) {
7693
- throw new Error("Runtime delegation CID does not match authorization bytes.");
7892
+ reject("cid_mismatch", "Runtime delegation CID does not match authorization bytes.");
7893
+ }
7894
+ const compact = authorizationWithoutBearer(authorization).split(".").length === 3;
7895
+ let signed;
7896
+ try {
7897
+ signed = compact ? signedAuthorityFromCompactUcan(authorization) : signedAuthorityFromSessionGrant(node, delegation, authorization, cid);
7898
+ } catch (error) {
7899
+ if (error instanceof RuntimeDelegationRejectedError) throw error;
7900
+ reject("authority_invalid", error instanceof Error ? error.message : String(error));
7694
7901
  }
7695
- const signed = signedAuthorityFromCompactUcan(authorization);
7696
7902
  if (signed.expiry.getTime() <= Date.now()) {
7697
- throw new Error("Runtime delegation is expired.");
7903
+ reject("expired", "Runtime delegation is expired.");
7698
7904
  }
7699
7905
  if (delegation.expiry.getTime() !== signed.expiry.getTime()) {
7700
- throw new Error("Runtime delegation expiry does not match signed authority.");
7906
+ reject("authority_invalid", "Runtime delegation expiry does not match signed authority.");
7701
7907
  }
7702
7908
  if (!didPrincipalsMatch(signed.audience, node.sessionDid)) {
7703
- throw new Error(
7909
+ reject(
7910
+ "audience_mismatch",
7704
7911
  `Runtime delegation targets ${signed.audience} but this session is ${node.sessionDid}.`
7705
7912
  );
7706
7913
  }
7707
7914
  if (!didPrincipalsMatch(delegation.delegateDID, signed.audience)) {
7708
- throw new Error("Runtime delegation audience does not match signed authority.");
7915
+ reject("audience_mismatch", "Runtime delegation audience does not match signed authority.");
7916
+ }
7917
+ if (compact && !resourcesMatch(canonicalResourcesFromPortableDelegation(delegation), signed.resources)) {
7918
+ reject("resources_mismatch", "Runtime delegation resources do not match signed authority.");
7709
7919
  }
7710
- const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
7711
- if (!resourcesMatch(declaredResources, signed.resources)) {
7712
- throw new Error("Runtime delegation resources do not match signed authority.");
7920
+ if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
7921
+ throw new Error("Runtime delegation is not authorized for this runtime.");
7713
7922
  }
7714
7923
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7715
7924
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
7716
- throw new Error(
7925
+ reject(
7926
+ "cid_mismatch",
7717
7927
  "A different authorization is already installed for this runtime delegation CID."
7718
7928
  );
7719
7929
  }
@@ -7743,22 +7953,28 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
7743
7953
  })),
7744
7954
  expiry: new Date(signed.expiry),
7745
7955
  delegateDID: signed.audience,
7746
- host
7956
+ host,
7957
+ ...compact ? {} : { siweProof: delegation.siweProof }
7747
7958
  };
7748
- if (node.isSessionOnly === true && typeof node.did === "string" && principalDidEquals3(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7749
- await node.useDelegation(installedCandidate);
7750
- } else {
7751
- await node.useRuntimeDelegation(installedCandidate);
7959
+ try {
7960
+ if (node.isSessionOnly === true && typeof node.did === "string" && principalDidEquals3(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7961
+ await node.useDelegation(installedCandidate);
7962
+ } else {
7963
+ await node.useRuntimeDelegation(installedCandidate);
7964
+ }
7965
+ } catch (error) {
7966
+ reject("activation_rejected", error instanceof Error ? error.message : String(error));
7752
7967
  }
7753
7968
  const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7754
7969
  if (!installed) {
7755
- throw new Error("Runtime delegation activation did not install the validated authority.");
7970
+ reject("activation_rejected", "Runtime delegation activation did not install the validated authority.");
7756
7971
  }
7757
7972
  if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed.audience) || installed.expiry.getTime() !== signed.expiry.getTime() || !resourcesMatch(
7758
7973
  canonicalResourcesFromPortableDelegation(installed),
7759
7974
  signed.resources
7760
7975
  ) || normalizedHost(installed.host ?? "") !== host) {
7761
- throw new Error(
7976
+ reject(
7977
+ "activation_rejected",
7762
7978
  "Runtime delegation activation installed authority that differs from the validated authority."
7763
7979
  );
7764
7980
  }