@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.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
  }
@@ -3150,12 +3182,13 @@ var _TinyCloudNode = class _TinyCloudNode {
3150
3182
  if (!auth) {
3151
3183
  throw new Error("Account bootstrap requires an active wallet session");
3152
3184
  }
3185
+ const accountSpaceId = this.ownedSpaceId(import_sdk_core8.ACCOUNT_REGISTRY_SPACE);
3153
3186
  const skipped = auth.lastActivationSkippedSpaceIds;
3187
+ if (skipped.includes(accountSpaceId)) return { action: "run", mode: "fresh" };
3154
3188
  if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
3155
- return { action: "run", mode: "fresh" };
3189
+ return { action: "run", mode: "repair" };
3156
3190
  }
3157
3191
  try {
3158
- const accountSpaceId = this.ownedSpaceId(import_sdk_core8.ACCOUNT_REGISTRY_SPACE);
3159
3192
  const markerPermission = {
3160
3193
  service: "tinycloud.kv",
3161
3194
  space: accountSpaceId,
@@ -3167,7 +3200,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3167
3200
  }
3168
3201
  const marker = await this.readBootstrapCompletionMarker();
3169
3202
  if (marker.ok) {
3170
- if (this.isAcceptedBootstrapCompletionMarker(marker.data.data)) {
3203
+ if (this.isAcceptedBootstrapCompletionMarker(marker.data.data, steps.map((step) => step.id))) {
3171
3204
  return { action: "skip" };
3172
3205
  }
3173
3206
  return { action: "run", mode: "repair" };
@@ -3211,15 +3244,22 @@ var _TinyCloudNode = class _TinyCloudNode {
3211
3244
  marker
3212
3245
  );
3213
3246
  if (!written.ok) {
3214
- throw new Error(`Failed to write bootstrap completion marker: ${written.error.message}`);
3247
+ throw Object.assign(
3248
+ new Error(`Failed to write bootstrap completion marker: ${written.error.message}`),
3249
+ { cause: written.error }
3250
+ );
3215
3251
  }
3216
3252
  }
3217
- isAcceptedBootstrapCompletionMarker(value) {
3253
+ /**
3254
+ * A marker written before a canonical space was added triggers one bounded repair.
3255
+ */
3256
+ isAcceptedBootstrapCompletionMarker(value, canonicalStepIds) {
3218
3257
  if (typeof value !== "object" || value === null) return false;
3219
3258
  const entries = Object.entries(value);
3220
3259
  const markerValue = (name) => entries.find(([key]) => key === name)?.[1];
3221
3260
  const version = markerValue("v");
3222
- return typeof version === "number" && Number.isInteger(version) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version);
3261
+ const stepIds = markerValue("stepIds");
3262
+ return typeof version === "number" && Number.isInteger(version) && ACCEPTED_MARKER_VERSIONS.some((acceptedVersion) => acceptedVersion === version) && Array.isArray(stepIds) && canonicalStepIds.every((stepId) => stepIds.includes(stepId));
3223
3263
  }
3224
3264
  markerReadIsUnhostedSpace(meta) {
3225
3265
  return meta?.status === 404;
@@ -3257,17 +3297,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3257
3297
  rawAbilities
3258
3298
  });
3259
3299
  } catch (err) {
3260
- throw new Error(
3261
- `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3300
+ throw Object.assign(
3301
+ new Error(
3302
+ `Account bootstrap aborted: signature rejected for space "${step.space}". Cause: ${err instanceof Error ? err.message : String(err)}`
3303
+ ),
3304
+ { cause: err }
3262
3305
  );
3263
3306
  }
3264
3307
  sessions.set(step.space, session);
3265
3308
  }
3266
3309
  for (const step of steps) {
3267
3310
  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}`);
3311
+ const hosted = await auth.hostOwnedSpaceResult(step.spaceId, "bootstrap-host");
3312
+ if (!hosted.success) {
3313
+ throw Object.assign(
3314
+ new Error(`Failed to host bootstrap space ${step.spaceId}: ${describeHostFailure2(hosted)}`),
3315
+ { cause: hosted }
3316
+ );
3271
3317
  }
3272
3318
  }
3273
3319
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -3279,8 +3325,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3279
3325
  }
3280
3326
  const activated = await (0, import_sdk_core8.activateSessionWithHost)(host, session.delegationHeader);
3281
3327
  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"}`
3328
+ throw Object.assign(
3329
+ new Error(
3330
+ `Failed to activate bootstrap session for ${step.spaceId}: ${activated.success ? "space was skipped" : describeHostFailure2(activated)}`
3331
+ ),
3332
+ { cause: activated }
3284
3333
  );
3285
3334
  }
3286
3335
  this.registerBootstrapRuntimeGrant(
@@ -3293,33 +3342,43 @@ var _TinyCloudNode = class _TinyCloudNode {
3293
3342
  if (step.kind === "account-index-schema") {
3294
3343
  const ensured = await this.account.index.ensure();
3295
3344
  if (!ensured.ok) {
3296
- throw new Error(`Failed to create account index schema: ${ensured.error.message}`);
3345
+ throw Object.assign(new Error(`Failed to create account index schema: ${ensured.error.message}`), { cause: ensured.error });
3297
3346
  }
3298
3347
  }
3299
3348
  if (step.kind === "seed-spaces") {
3300
- const registered = await this.account.spaces.registerBatch(
3301
- step.spaces.map((space) => ({
3302
- spaceId: space.spaceId,
3303
- name: space.name,
3304
- ownerDid: this.did,
3305
- type: "owned",
3306
- permissions: ["*"],
3307
- status: "active"
3308
- }))
3309
- );
3310
- if (!registered.ok) {
3311
- throw new Error(
3312
- `Failed to seed account spaces: ${registered.error.message}`
3313
- );
3314
- }
3315
- const batchError = registered.data.recoveredFromBatchError;
3316
- if (batchError) {
3317
- warnings.push({
3318
- stepId: step.id,
3319
- kind: "batch-write-reconciled",
3320
- code: batchError.code,
3321
- message: batchError.message
3322
- });
3349
+ const inputs = step.spaces.map((space) => ({
3350
+ spaceId: space.spaceId,
3351
+ name: space.name,
3352
+ ownerDid: this.did,
3353
+ type: "owned",
3354
+ permissions: ["*"],
3355
+ status: "active"
3356
+ }));
3357
+ if (mode === "fresh") {
3358
+ const registered = await this.account.spaces.registerBatch(inputs);
3359
+ if (!registered.ok) {
3360
+ throw Object.assign(
3361
+ new Error(`Failed to seed account spaces: ${registered.error.message}`),
3362
+ { cause: registered.error }
3363
+ );
3364
+ }
3365
+ const batchError = registered.data.recoveredFromBatchError;
3366
+ if (batchError) {
3367
+ warnings.push({
3368
+ stepId: step.id,
3369
+ kind: "batch-write-reconciled",
3370
+ code: batchError.code,
3371
+ message: batchError.message
3372
+ });
3373
+ }
3374
+ } else {
3375
+ const ensured = await this.account.spaces.registerMissing(inputs);
3376
+ if (!ensured.ok) {
3377
+ throw Object.assign(
3378
+ new Error(`Failed to seed account spaces: ${ensured.error.message}`),
3379
+ { cause: ensured.error }
3380
+ );
3381
+ }
3323
3382
  }
3324
3383
  }
3325
3384
  if (step.kind === "seed-applications") {
@@ -3330,7 +3389,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3330
3389
  { assumeUnregistered: true }
3331
3390
  );
3332
3391
  if (!registered.ok) {
3333
- throw new Error(`Failed to seed bootstrap applications: ${registered.error.message}`);
3392
+ throw Object.assign(new Error(`Failed to seed bootstrap applications: ${registered.error.message}`), { cause: registered.error });
3334
3393
  }
3335
3394
  }
3336
3395
  if (step.kind === "encryption-network-create") {
@@ -3348,8 +3407,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3348
3407
  ]
3349
3408
  });
3350
3409
  if (!migrated.ok) {
3351
- throw new Error(
3352
- `Failed to create secret_records schema: ${migrated.error.message}`
3410
+ throw Object.assign(
3411
+ new Error(`Failed to create secret_records schema: ${migrated.error.message}`),
3412
+ { cause: migrated.error }
3353
3413
  );
3354
3414
  }
3355
3415
  }
@@ -3616,6 +3676,10 @@ var _TinyCloudNode = class _TinyCloudNode {
3616
3676
  );
3617
3677
  return;
3618
3678
  }
3679
+ 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)) {
3680
+ console.warn("TinyCloud account registry sync stopped: storage is full", error);
3681
+ return;
3682
+ }
3619
3683
  lastError = error;
3620
3684
  if (attempt < delays.length - 1) {
3621
3685
  await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
@@ -3690,14 +3754,14 @@ var _TinyCloudNode = class _TinyCloudNode {
3690
3754
  }
3691
3755
  if (!activation.success && activation.status !== 404) {
3692
3756
  throw Object.assign(
3693
- new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure(activation)}`),
3757
+ new Error(`Failed to check owned space ${spaceId}: ${describeHostFailure2(activation)}`),
3694
3758
  { cause: activation }
3695
3759
  );
3696
3760
  }
3697
3761
  const created = await this.auth.hostOwnedSpaceResult(spaceId);
3698
3762
  if (!created.success) {
3699
3763
  throw Object.assign(
3700
- new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure(created)}`),
3764
+ new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
3701
3765
  { cause: created }
3702
3766
  );
3703
3767
  }
@@ -3706,7 +3770,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3706
3770
  if (!retry.success || retry.skipped?.includes(spaceId)) {
3707
3771
  throw Object.assign(
3708
3772
  new Error(
3709
- `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure(retry)}`
3773
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.success ? "space was skipped" : describeHostFailure2(retry)}`
3710
3774
  ),
3711
3775
  { cause: retry }
3712
3776
  );
@@ -3743,19 +3807,23 @@ var _TinyCloudNode = class _TinyCloudNode {
3743
3807
  if (!host) {
3744
3808
  throw new Error("Owned space hosting requires a TinyCloud host");
3745
3809
  }
3746
- const hosted = await this.auth.hostOwnedSpace(
3747
- spaceId
3748
- );
3749
- if (!hosted) {
3750
- throw new Error(`Failed to host owned space: ${spaceId}`);
3810
+ const hosted = await this.auth.hostOwnedSpaceResult(spaceId);
3811
+ if (!hosted.success) {
3812
+ throw Object.assign(
3813
+ new Error(`Failed to host owned space ${spaceId}: ${describeHostFailure2(hosted)}`),
3814
+ { cause: hosted }
3815
+ );
3751
3816
  }
3752
3817
  const activation = await (0, import_sdk_core8.activateSessionWithHost)(
3753
3818
  host,
3754
3819
  session.delegationHeader
3755
3820
  );
3756
3821
  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"}`
3822
+ throw Object.assign(
3823
+ new Error(
3824
+ `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
3825
+ ),
3826
+ { cause: activation }
3759
3827
  );
3760
3828
  }
3761
3829
  try {
@@ -3951,23 +4019,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3951
4019
  throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3952
4020
  }
3953
4021
  })();
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
- }));
4022
+ stagedRecap = exactVerifiedRecap(verified);
3971
4023
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3972
4024
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3973
4025
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
@@ -4771,7 +4823,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4771
4823
  if (cached) return cached;
4772
4824
  const response = await fetch(`${this.config.host}/info`);
4773
4825
  if (!response.ok) {
4774
- throw new Error(`Failed to fetch node info: HTTP ${response.status}`);
4826
+ throw await (0, import_sdk_core8.httpResponseError)(response, "Failed to fetch node info");
4775
4827
  }
4776
4828
  const info = await response.json();
4777
4829
  if (typeof info.nodeId !== "string" || info.nodeId.length === 0) {
@@ -4821,9 +4873,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4821
4873
  );
4822
4874
  if (response.status === 404) return null;
4823
4875
  if (!response.ok) {
4824
- throw new Error(
4825
- `Failed to fetch encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
4826
- );
4876
+ throw await (0, import_sdk_core8.httpResponseError)(response, `Failed to fetch encryption network ${networkId}`);
4827
4877
  }
4828
4878
  const body = await response.json();
4829
4879
  return "descriptor" in body && body.descriptor ? body.descriptor : body;
@@ -5324,7 +5374,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5324
5374
  headers: { accept: "application/json", "content-type": "application/json", authorization },
5325
5375
  body: (0, import_sdk_core8.canonicalizeEncryptionJson)(request)
5326
5376
  });
5327
- if (!response.ok) throw new Error(`V3 share delivery authorization failed: ${response.status}`);
5377
+ if (!response.ok) throw await (0, import_sdk_core8.httpResponseError)(response, "V3 share delivery authorization failed");
5328
5378
  const verified = (0, import_sdk_core8.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
5329
5379
  request,
5330
5380
  senderKeyDid: this.credentialHolderDid,
@@ -5546,9 +5596,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5546
5596
  );
5547
5597
  }
5548
5598
  if (!response.ok) {
5549
- throw new Error(
5550
- `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
5551
- );
5599
+ throw await (0, import_sdk_core8.httpResponseError)(response, `Failed to create encryption network ${networkId}`);
5552
5600
  }
5553
5601
  const created = await response.json();
5554
5602
  return created.descriptor;
@@ -5832,6 +5880,42 @@ var _TinyCloudNode = class _TinyCloudNode {
5832
5880
  (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
5881
  );
5834
5882
  }
5883
+ /**
5884
+ * Verify a wallet-signed session grant (a CACAO built from a SIWE and its
5885
+ * EIP-191 signature) issued to this instance's live session key. The
5886
+ * verifier rebuilds the CACAO from `siwe` and `signature`, so the grant is
5887
+ * accepted only when its authorization bytes and CID are exactly that CACAO,
5888
+ * its signer is `address`, its audience is this session key, it is valid
5889
+ * now, and its ReCap covers `spaceId` or names only raw encryption networks
5890
+ * (a decrypt-only grant has no space). Returns the signed ReCap and expiry.
5891
+ * Bindings without `validateSessionGrant` fall back to the session verifier,
5892
+ * which also refuses decrypt-only grants.
5893
+ */
5894
+ verifySessionGrant(grant) {
5895
+ const bindings = this.wasmBindings;
5896
+ const validate = typeof bindings.validateSessionGrant === "function" ? bindings.validateSessionGrant.bind(bindings) : bindings.validatePersistedSession?.bind(bindings);
5897
+ if (validate === void 0) {
5898
+ throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
5899
+ }
5900
+ const verified = validate({
5901
+ delegationHeader: { Authorization: grant.delegationHeader.Authorization },
5902
+ delegationCid: grant.delegationCid,
5903
+ spaceId: grant.spaceId,
5904
+ jwk: this.sessionKeyJwk,
5905
+ address: (0, import_sdk_core8.canonicalizeAddress)(grant.address),
5906
+ chainId: grant.chainId,
5907
+ siwe: grant.siwe,
5908
+ signature: grant.signature
5909
+ });
5910
+ if (typeof verified.expiresAt !== "string") {
5911
+ throw new Error("Session grant has no signed expiry.");
5912
+ }
5913
+ const expiresAt = new Date(verified.expiresAt);
5914
+ if (!Number.isFinite(expiresAt.getTime())) {
5915
+ throw new Error("Session grant has an invalid signed expiry.");
5916
+ }
5917
+ return { recap: exactVerifiedRecap(verified), expiresAt };
5918
+ }
5835
5919
  /**
5836
5920
  * Install a portable runtime permission delegation into this SDK instance so
5837
5921
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -5856,8 +5940,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5856
5940
  delegation.delegationHeader
5857
5941
  );
5858
5942
  if (!activateResult.success) {
5859
- throw new Error(
5860
- `Failed to activate runtime permission delegation: ${activateResult.error}`
5943
+ throw Object.assign(
5944
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
5945
+ { cause: activateResult }
5861
5946
  );
5862
5947
  }
5863
5948
  this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
@@ -5953,8 +6038,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5953
6038
  delegatedSession.delegationHeader
5954
6039
  );
5955
6040
  if (!activateResult.success) {
5956
- throw new Error(
5957
- `Failed to activate runtime permission delegation: ${activateResult.error}`
6041
+ throw Object.assign(
6042
+ new Error(`Failed to activate runtime permission delegation: ${describeHostFailure2(activateResult)}`),
6043
+ { cause: activateResult }
5958
6044
  );
5959
6045
  }
5960
6046
  const delegation = this.runtimeDelegationFromSession(
@@ -5962,7 +6048,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5962
6048
  delegatedEntries,
5963
6049
  spaceId,
5964
6050
  session,
5965
- expiresAt
6051
+ expiresAt,
6052
+ { siwe: prepared.siwe, signature }
5966
6053
  );
5967
6054
  this.runtimePermissionGrants.push({
5968
6055
  session: {
@@ -6139,7 +6226,10 @@ var _TinyCloudNode = class _TinyCloudNode {
6139
6226
  delegationSession.delegationHeader
6140
6227
  );
6141
6228
  if (!activateResult.success) {
6142
- throw new Error(`Failed to activate public space delegation: ${activateResult.error}`);
6229
+ throw Object.assign(
6230
+ new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
6231
+ { cause: activateResult }
6232
+ );
6143
6233
  }
6144
6234
  if (this._capabilityRegistry && this.session) {
6145
6235
  const sessionKey = {
@@ -6492,8 +6582,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6492
6582
  delegationHeader
6493
6583
  );
6494
6584
  if (!activateResult.success) {
6495
- throw new Error(
6496
- `Failed to activate delegation with host: ${activateResult.error}`
6585
+ throw Object.assign(
6586
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6587
+ { cause: activateResult }
6497
6588
  );
6498
6589
  }
6499
6590
  return {
@@ -6529,8 +6620,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6529
6620
  delegationHeader
6530
6621
  );
6531
6622
  if (!activateResult.success) {
6532
- throw new Error(
6533
- `Failed to activate delegation with host: ${activateResult.error}`
6623
+ throw Object.assign(
6624
+ new Error(`Failed to activate delegation with host: ${describeHostFailure2(activateResult)}`),
6625
+ { cause: activateResult }
6534
6626
  );
6535
6627
  }
6536
6628
  return {
@@ -6743,7 +6835,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6743
6835
  }
6744
6836
  return grants;
6745
6837
  }
6746
- runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
6838
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt, siweProof) {
6747
6839
  const resources = this.delegatedResourcesForEntries(entries, spaceId);
6748
6840
  const primary = resources[0];
6749
6841
  return {
@@ -6758,7 +6850,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6758
6850
  delegateDID: session.verificationMethod,
6759
6851
  ownerAddress: session.address,
6760
6852
  chainId: session.chainId,
6761
- host: this.config.host
6853
+ host: this.config.host,
6854
+ siweProof
6762
6855
  };
6763
6856
  }
6764
6857
  runtimeGrantFromDelegation(delegation, session) {
@@ -6951,6 +7044,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6951
7044
  if (grantedAction === requestedAction) {
6952
7045
  return true;
6953
7046
  }
7047
+ if ((0, import_sdk_core8.isExplicitOnlyAction)(requestedAction)) {
7048
+ return false;
7049
+ }
6954
7050
  if (grantedAction.endsWith("/*")) {
6955
7051
  const prefix = grantedAction.slice(0, -2);
6956
7052
  return requestedAction.startsWith(`${prefix}/`);
@@ -7137,7 +7233,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7137
7233
  delegationSession.delegationHeader
7138
7234
  );
7139
7235
  if (!activateResult.success) {
7140
- throw new Error(`Failed to activate delegation: ${activateResult.error}`);
7236
+ throw Object.assign(
7237
+ new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
7238
+ { cause: activateResult }
7239
+ );
7141
7240
  }
7142
7241
  const result = {
7143
7242
  cid: delegationSession.delegationCid,
@@ -7287,7 +7386,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7287
7386
  invokerSession.delegationHeader
7288
7387
  );
7289
7388
  if (!activateResult.success) {
7290
- throw new Error(`Failed to activate delegated session: ${activateResult.error}`);
7389
+ throw Object.assign(
7390
+ new Error(`Failed to activate delegated session: ${describeHostFailure2(activateResult)}`),
7391
+ { cause: activateResult }
7392
+ );
7291
7393
  }
7292
7394
  const session = {
7293
7395
  address: mySession.address,
@@ -7404,7 +7506,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7404
7506
  subDelegationSession.delegationHeader
7405
7507
  );
7406
7508
  if (!activateResult.success) {
7407
- throw new Error(`Failed to activate sub-delegation: ${activateResult.error}`);
7509
+ throw Object.assign(
7510
+ new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
7511
+ { cause: activateResult }
7512
+ );
7408
7513
  }
7409
7514
  return {
7410
7515
  cid: subDelegationSession.delegationCid,
@@ -7445,6 +7550,16 @@ var import_sdk_core12 = require("@tinycloud/sdk-core");
7445
7550
 
7446
7551
  // src/delegation.ts
7447
7552
  var import_sdk_core9 = require("@tinycloud/sdk-core");
7553
+ var RuntimeDelegationRejectedError = class extends Error {
7554
+ constructor(reason, message) {
7555
+ super(message);
7556
+ this.name = "RuntimeDelegationRejectedError";
7557
+ this.reason = reason;
7558
+ }
7559
+ };
7560
+ function reject(reason, message) {
7561
+ throw new RuntimeDelegationRejectedError(reason, message);
7562
+ }
7448
7563
  var SERVICE_BY_SHORT_NAME = {
7449
7564
  kv: "tinycloud.kv",
7450
7565
  sql: "tinycloud.sql",
@@ -7591,6 +7706,15 @@ function signedAuthorityFromCompactUcan(authorization) {
7591
7706
  abilities
7592
7707
  ));
7593
7708
  }
7709
+ return canonicalSignedAuthority({
7710
+ issuer: payload.iss,
7711
+ audience: payload.aud,
7712
+ expiry,
7713
+ proofs: [...payload.prf],
7714
+ ...remainingRedelegationDepth === void 0 ? {} : { remainingRedelegationDepth }
7715
+ }, permissions);
7716
+ }
7717
+ function canonicalSignedAuthority(signed, permissions) {
7594
7718
  if (permissions.length === 0) {
7595
7719
  throw new Error("Validated runtime delegation has no signed capabilities.");
7596
7720
  }
@@ -7602,15 +7726,84 @@ function signedAuthorityFromCompactUcan(authorization) {
7602
7726
  actions: [...permission.actions],
7603
7727
  ...permission.caveats === void 0 ? {} : { caveats: permission.caveats }
7604
7728
  }));
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
- };
7729
+ return { ...signed, permissions: canonicalPermissions, resources: canonicalResources };
7730
+ }
7731
+ var RAW_ENCRYPTION_PREFIX = "urn:tinycloud:encryption:";
7732
+ var PKH_SPACE = /^tinycloud:pkh:eip155:(\d+):(0x[0-9a-fA-F]{40}):(.+)$/;
7733
+ function comparableSpace(space) {
7734
+ const match = PKH_SPACE.exec(space);
7735
+ return match ? `tinycloud:pkh:eip155:${match[1]}:${match[2].toLowerCase()}:${match[3]}` : space;
7736
+ }
7737
+ function signedAuthorityFromSessionGrant(node, delegation, authorization, cid) {
7738
+ const proof = delegation.siweProof;
7739
+ if (proof === void 0 || proof === null || typeof proof !== "object" || typeof proof.siwe !== "string" || typeof proof.signature !== "string") {
7740
+ reject(
7741
+ "proof_missing",
7742
+ "Runtime delegation is a wallet-signed session grant stored without its signed SIWE proof, so its authority cannot be verified."
7743
+ );
7744
+ }
7745
+ if (typeof node.verifySessionGrant !== "function") {
7746
+ reject("unsupported", "This runtime cannot verify wallet-signed session grants.");
7747
+ }
7748
+ const owner = `did:pkh:eip155:${delegation.chainId}:${delegation.ownerAddress}`;
7749
+ if (typeof node.did !== "string" || !didPrincipalsMatch(owner, node.did)) {
7750
+ reject("owner_mismatch", "Runtime delegation is not signed by this session's owner.");
7751
+ }
7752
+ let verified;
7753
+ try {
7754
+ verified = node.verifySessionGrant({
7755
+ delegationHeader: { Authorization: authorization },
7756
+ delegationCid: cid,
7757
+ spaceId: delegation.spaceId,
7758
+ address: delegation.ownerAddress,
7759
+ chainId: delegation.chainId,
7760
+ siwe: proof.siwe,
7761
+ signature: proof.signature
7762
+ });
7763
+ } catch (error) {
7764
+ reject("proof_invalid", error instanceof Error ? error.message : String(error));
7765
+ }
7766
+ const space = comparableSpace(delegation.spaceId);
7767
+ const spaceOwner = PKH_SPACE.exec(delegation.spaceId);
7768
+ if (!spaceOwner || !didPrincipalsMatch(`did:pkh:eip155:${spaceOwner[1]}:${spaceOwner[2]}`, owner)) {
7769
+ reject("authority_invalid", "Runtime delegation space is not owned by its signer.");
7770
+ }
7771
+ const groups = /* @__PURE__ */ new Map();
7772
+ for (const entry of verified.recap) {
7773
+ const service = entry.service.startsWith("tinycloud.") ? entry.service : `tinycloud.${entry.service}`;
7774
+ const raw = entry.space === "encryption";
7775
+ if (raw) {
7776
+ if (service !== "tinycloud.encryption" || !entry.path.startsWith(RAW_ENCRYPTION_PREFIX)) {
7777
+ reject("authority_invalid", "Runtime delegation has an unsupported signed raw resource.");
7778
+ }
7779
+ const networkOwner = entry.path.slice(RAW_ENCRYPTION_PREFIX.length, entry.path.lastIndexOf(":"));
7780
+ if (!didPrincipalsMatch(networkOwner, owner)) {
7781
+ reject("authority_invalid", "Runtime delegation names an encryption network its signer does not own.");
7782
+ }
7783
+ } else if (service === "tinycloud.encryption" || comparableSpace(entry.space) !== space) {
7784
+ reject("authority_invalid", "Runtime delegation has signed authority outside its space.");
7785
+ }
7786
+ const caveats = signedCaveats(entry.caveats ?? []);
7787
+ const key = JSON.stringify([service, raw ? "" : entry.space, entry.path, (0, import_sdk_core9.canonicalizeRecapCaveats)(caveats)]);
7788
+ const group = groups.get(key) ?? {
7789
+ service,
7790
+ ...raw ? {} : { space: entry.space },
7791
+ path: entry.path,
7792
+ actions: [],
7793
+ ...caveats === void 0 ? {} : { caveats }
7794
+ };
7795
+ for (const action of entry.actions) {
7796
+ if (!group.actions.includes(action)) group.actions.push(action);
7797
+ }
7798
+ groups.set(key, group);
7799
+ }
7800
+ return canonicalSignedAuthority({
7801
+ issuer: owner,
7802
+ // The verifier bound the SIWE audience to this runtime's own session key.
7803
+ audience: node.sessionDid,
7804
+ expiry: verified.expiresAt,
7805
+ proofs: []
7806
+ }, [...groups.values()]);
7614
7807
  }
7615
7808
  function canonicalResourcesFromPortableDelegation(delegation) {
7616
7809
  if (delegation.resources !== void 0 && delegation.resources.length > 0) {
@@ -7646,46 +7839,58 @@ function resourcesMatch(actual, expected) {
7646
7839
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
7647
7840
  const host = normalizedHost(options.host);
7648
7841
  if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
7649
- throw new Error(
7842
+ reject(
7843
+ "host_mismatch",
7650
7844
  `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
7651
7845
  );
7652
7846
  }
7653
7847
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
7654
- throw new Error("Runtime delegation has an invalid expiry.");
7848
+ reject("authority_invalid", "Runtime delegation has an invalid expiry.");
7655
7849
  }
7656
7850
  if (delegation.expiry.getTime() <= Date.now()) {
7657
- throw new Error("Runtime delegation is expired.");
7851
+ reject("expired", "Runtime delegation is expired.");
7658
7852
  }
7659
7853
  const authorization = delegation.delegationHeader?.Authorization;
7660
7854
  if (typeof authorization !== "string" || authorization.length === 0) {
7661
- throw new Error("Runtime delegation is missing authorization bytes.");
7855
+ reject("authority_invalid", "Runtime delegation is missing authorization bytes.");
7662
7856
  }
7663
7857
  const cid = node.computeDelegationCid(authorization);
7664
7858
  if (cid !== delegation.cid) {
7665
- throw new Error("Runtime delegation CID does not match authorization bytes.");
7859
+ reject("cid_mismatch", "Runtime delegation CID does not match authorization bytes.");
7860
+ }
7861
+ const compact = authorizationWithoutBearer(authorization).split(".").length === 3;
7862
+ let signed;
7863
+ try {
7864
+ signed = compact ? signedAuthorityFromCompactUcan(authorization) : signedAuthorityFromSessionGrant(node, delegation, authorization, cid);
7865
+ } catch (error) {
7866
+ if (error instanceof RuntimeDelegationRejectedError) throw error;
7867
+ reject("authority_invalid", error instanceof Error ? error.message : String(error));
7666
7868
  }
7667
- const signed = signedAuthorityFromCompactUcan(authorization);
7668
7869
  if (signed.expiry.getTime() <= Date.now()) {
7669
- throw new Error("Runtime delegation is expired.");
7870
+ reject("expired", "Runtime delegation is expired.");
7670
7871
  }
7671
7872
  if (delegation.expiry.getTime() !== signed.expiry.getTime()) {
7672
- throw new Error("Runtime delegation expiry does not match signed authority.");
7873
+ reject("authority_invalid", "Runtime delegation expiry does not match signed authority.");
7673
7874
  }
7674
7875
  if (!didPrincipalsMatch(signed.audience, node.sessionDid)) {
7675
- throw new Error(
7876
+ reject(
7877
+ "audience_mismatch",
7676
7878
  `Runtime delegation targets ${signed.audience} but this session is ${node.sessionDid}.`
7677
7879
  );
7678
7880
  }
7679
7881
  if (!didPrincipalsMatch(delegation.delegateDID, signed.audience)) {
7680
- throw new Error("Runtime delegation audience does not match signed authority.");
7882
+ reject("audience_mismatch", "Runtime delegation audience does not match signed authority.");
7883
+ }
7884
+ if (compact && !resourcesMatch(canonicalResourcesFromPortableDelegation(delegation), signed.resources)) {
7885
+ reject("resources_mismatch", "Runtime delegation resources do not match signed authority.");
7681
7886
  }
7682
- const declaredResources = canonicalResourcesFromPortableDelegation(delegation);
7683
- if (!resourcesMatch(declaredResources, signed.resources)) {
7684
- throw new Error("Runtime delegation resources do not match signed authority.");
7887
+ if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
7888
+ throw new Error("Runtime delegation is not authorized for this runtime.");
7685
7889
  }
7686
7890
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7687
7891
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
7688
- throw new Error(
7892
+ reject(
7893
+ "cid_mismatch",
7689
7894
  "A different authorization is already installed for this runtime delegation CID."
7690
7895
  );
7691
7896
  }
@@ -7715,22 +7920,28 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
7715
7920
  })),
7716
7921
  expiry: new Date(signed.expiry),
7717
7922
  delegateDID: signed.audience,
7718
- host
7923
+ host,
7924
+ ...compact ? {} : { siweProof: delegation.siweProof }
7719
7925
  };
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);
7926
+ try {
7927
+ if (node.isSessionOnly === true && typeof node.did === "string" && (0, import_sdk_core9.principalDidEquals)(node.did, node.sessionDid) && typeof node.useDelegation === "function") {
7928
+ await node.useDelegation(installedCandidate);
7929
+ } else {
7930
+ await node.useRuntimeDelegation(installedCandidate);
7931
+ }
7932
+ } catch (error) {
7933
+ reject("activation_rejected", error instanceof Error ? error.message : String(error));
7724
7934
  }
7725
7935
  const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
7726
7936
  if (!installed) {
7727
- throw new Error("Runtime delegation activation did not install the validated authority.");
7937
+ reject("activation_rejected", "Runtime delegation activation did not install the validated authority.");
7728
7938
  }
7729
7939
  if (installed.delegationHeader.Authorization !== authorization || !didPrincipalsMatch(installed.delegateDID, signed.audience) || installed.expiry.getTime() !== signed.expiry.getTime() || !resourcesMatch(
7730
7940
  canonicalResourcesFromPortableDelegation(installed),
7731
7941
  signed.resources
7732
7942
  ) || normalizedHost(installed.host ?? "") !== host) {
7733
- throw new Error(
7943
+ reject(
7944
+ "activation_rejected",
7734
7945
  "Runtime delegation activation installed authority that differs from the validated authority."
7735
7946
  );
7736
7947
  }