@tinycloud/node-sdk 3.0.0-beta.9 → 3.0.0

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
@@ -50,6 +50,7 @@ __export(core_exports, {
50
50
  DuckDbService: () => import_sdk_core15.DuckDbService,
51
51
  FileSessionStorage: () => FileSessionStorage,
52
52
  IdentityParseError: () => import_sdk_core11.IdentityParseError,
53
+ InvalidRestoredSessionError: () => InvalidRestoredSessionError,
53
54
  KVService: () => import_sdk_core13.KVService,
54
55
  ManifestValidationError: () => import_sdk_core12.ManifestValidationError,
55
56
  MemorySessionStorage: () => MemorySessionStorage,
@@ -2285,6 +2286,22 @@ function cloneRecapCaveats(caveats) {
2285
2286
  throw new Error("Verified persisted session ReCap contains invalid caveats.");
2286
2287
  }
2287
2288
  }
2289
+ function jsonFromVerifier(value) {
2290
+ if (value === void 0) return null;
2291
+ if (Array.isArray(value)) return value.map(jsonFromVerifier);
2292
+ if (value instanceof Map) {
2293
+ const entries = [...value.entries()];
2294
+ if (!entries.every(([key]) => typeof key === "string")) return value;
2295
+ return Object.fromEntries(entries.map(([key, nested]) => [key, jsonFromVerifier(nested)]));
2296
+ }
2297
+ if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
2298
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, jsonFromVerifier(nested)]));
2299
+ }
2300
+ return value;
2301
+ }
2302
+ function verifierRecapEntries(entries) {
2303
+ return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
2304
+ }
2288
2305
  var ServiceGraphLifetime = class {
2289
2306
  constructor(invokeFn, invokeAnyFn, fetchFn) {
2290
2307
  this.invokeFn = invokeFn;
@@ -2357,18 +2374,23 @@ var UnsupportedSessionRestoreError = class extends Error {
2357
2374
  this.name = "UnsupportedSessionRestoreError";
2358
2375
  }
2359
2376
  };
2377
+ var InvalidRestoredSessionError = class extends Error {
2378
+ constructor(message = "Persisted session authority is invalid or expired.") {
2379
+ super(message);
2380
+ this.code = "AUTH_EXPIRED";
2381
+ this.name = "InvalidRestoredSessionError";
2382
+ }
2383
+ };
2360
2384
  function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
2361
2385
  if (typeof persistedVerificationMethod !== "string") return void 0;
2362
2386
  const principal = canonicalVerificationMethod.split("#", 1)[0];
2363
2387
  return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
2364
2388
  }
2365
2389
  function persistedExpiry(value) {
2366
- if (typeof value !== "string") {
2367
- throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
2368
- }
2390
+ if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
2369
2391
  const expiry = new Date(value);
2370
2392
  if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
2371
- throw new Error("Persisted session expiry is invalid or expired.");
2393
+ throw new InvalidRestoredSessionError();
2372
2394
  }
2373
2395
  return expiry;
2374
2396
  }
@@ -2911,16 +2933,26 @@ var _TinyCloudNode = class _TinyCloudNode {
2911
2933
  */
2912
2934
  getVerifiedSessionCapabilities() {
2913
2935
  const session = this.currentTinyCloudSession();
2914
- if (!session || !session.siwe) return [];
2915
- return (0, import_sdk_core8.parseRecapCapabilities)(
2916
- (siwe) => this.parseRecapWithCaveats(siwe),
2917
- session.siwe
2918
- ).map((entry) => entry.service === "tinycloud.encryption" ? {
2919
- service: entry.service,
2920
- path: entry.path,
2921
- actions: [...entry.actions],
2922
- ...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
2923
- } : entry);
2936
+ return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
2937
+ }
2938
+ /** Normalize signed capabilities without collapsing nested encryption into raw authority. */
2939
+ projectSignedRecapCapabilities(siwe) {
2940
+ const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
2941
+ const normalized = (0, import_sdk_core8.parseRecapCapabilities)(() => [entry], siwe)[0];
2942
+ if (normalized.service !== "tinycloud.encryption") return normalized;
2943
+ if (!this.isEncryptionNetworkOperation("encryption", entry.path, entry.space)) {
2944
+ return { ...normalized, space: entry.space };
2945
+ }
2946
+ return {
2947
+ service: normalized.service,
2948
+ path: normalized.path,
2949
+ actions: normalized.actions,
2950
+ ...normalized.caveats === void 0 ? {} : { caveats: normalized.caveats }
2951
+ };
2952
+ });
2953
+ return projected.sort(
2954
+ (left, right) => (left.space ?? "").localeCompare(right.space ?? "") || left.service.localeCompare(right.service) || left.path.localeCompare(right.path)
2955
+ );
2924
2956
  }
2925
2957
  /**
2926
2958
  * Get the Ethereum address for this user.
@@ -3375,9 +3407,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3375
3407
  * We must keep the full owner-scoped URI so a synthetic primary grant can
3376
3408
  * never cover an operation on a different owner's space.
3377
3409
  *
3378
- * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
3379
- * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
3380
- * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
3410
+ * Only top-level encryption network entries (`space: "encryption"` or
3411
+ * absent) become `resource` ops. A network-shaped path nested under a
3412
+ * data space remains a `spaceId` op, not raw network authority.
3381
3413
  * One op per action.
3382
3414
  *
3383
3415
  * Returns `[]` for session-only / restored-without-siwe modes and for any
@@ -3398,7 +3430,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3398
3430
  operations = entries.flatMap((entry) => {
3399
3431
  const service = this.invocationServiceName(entry.service);
3400
3432
  return entry.actions.map((action) => ({
3401
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
3433
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
3402
3434
  service,
3403
3435
  path: entry.path,
3404
3436
  action,
@@ -3560,7 +3592,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3560
3592
  return;
3561
3593
  } catch (error) {
3562
3594
  const message = error instanceof Error ? error.message : String(error);
3563
- if (/Unauthorized Action|\b401\b/.test(message)) {
3595
+ if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
3564
3596
  console.warn(
3565
3597
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
3566
3598
  error
@@ -3810,9 +3842,14 @@ var _TinyCloudNode = class _TinyCloudNode {
3810
3842
  */
3811
3843
  async restoreSession(sessionData) {
3812
3844
  await this.wasmBindings.ensureInitialized?.();
3813
- const restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
3845
+ let restoredJwk;
3846
+ try {
3847
+ restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
3848
+ } catch {
3849
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
3850
+ }
3814
3851
  if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
3815
- throw new Error("Persisted session chain ID must be a positive safe integer.");
3852
+ throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
3816
3853
  }
3817
3854
  const stagedManager = this.wasmBindings.createSessionManager();
3818
3855
  const stagedReplace = stagedManager.replaceSessionKey;
@@ -3824,32 +3861,31 @@ var _TinyCloudNode = class _TinyCloudNode {
3824
3861
  let stagedKeyId;
3825
3862
  let stagedJwk;
3826
3863
  let canonicalVerificationMethod;
3864
+ const keyIds = liveKeys.call(this.sessionManager);
3865
+ if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
3866
+ throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
3867
+ }
3868
+ for (const keyId of keyIds) {
3869
+ if (keyId === this.sessionKeyId) continue;
3870
+ const jwk = this.sessionManager.jwk(keyId);
3871
+ if (!jwk) throw new Error("missing live session key");
3872
+ stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
3873
+ }
3827
3874
  try {
3828
- const keyIds = liveKeys.call(this.sessionManager);
3829
- if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
3830
- throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
3831
- }
3832
- for (const keyId of keyIds) {
3833
- if (keyId === this.sessionKeyId) continue;
3834
- const jwk = this.sessionManager.jwk(keyId);
3835
- if (!jwk) throw new Error("missing live session key");
3836
- stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
3837
- }
3838
3875
  stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
3839
- const stagedJwkJson = stagedManager.jwk(stagedKeyId);
3840
- if (!stagedJwkJson) throw new Error("missing restored session key");
3841
- stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
3842
- canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
3843
- } catch (error) {
3844
- if (error instanceof UnsupportedSessionRestoreError) throw error;
3845
- throw new Error("Persisted session has an invalid private Ed25519 session key.");
3876
+ } catch {
3877
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
3846
3878
  }
3879
+ const stagedJwkJson = stagedManager.jwk(stagedKeyId);
3880
+ if (!stagedJwkJson) throw new Error("missing restored session key");
3881
+ stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
3882
+ canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
3847
3883
  const restoredVerificationMethod = canonicalRestoredVerificationMethod(
3848
3884
  canonicalVerificationMethod,
3849
3885
  sessionData.verificationMethod
3850
3886
  );
3851
3887
  if (!restoredVerificationMethod) {
3852
- throw new Error(
3888
+ throw new InvalidRestoredSessionError(
3853
3889
  "Persisted session verification method does not match its private Ed25519 session key."
3854
3890
  );
3855
3891
  }
@@ -3861,26 +3897,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3861
3897
  ];
3862
3898
  const hasPersistedProof = proofValues.every((value) => value !== void 0);
3863
3899
  if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
3864
- throw new Error("Persisted session authority metadata is incomplete.");
3900
+ throw new InvalidRestoredSessionError("Persisted session authority metadata is incomplete.");
3901
+ }
3902
+ let restoredAddress;
3903
+ try {
3904
+ restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
3905
+ } catch {
3906
+ throw new InvalidRestoredSessionError("Persisted session has an invalid wallet address.");
3865
3907
  }
3866
- const restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
3867
3908
  let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
3868
3909
  let stagedRecap = [];
3869
3910
  if (hasPersistedProof) {
3870
3911
  if (typeof this.wasmBindings.validatePersistedSession !== "function") {
3871
3912
  throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
3872
3913
  }
3873
- const verified = this.wasmBindings.validatePersistedSession({
3874
- delegationHeader: sessionData.delegationHeader,
3875
- delegationCid: sessionData.delegationCid,
3876
- spaceId: sessionData.spaceId,
3877
- jwk: stagedJwk,
3878
- address: restoredAddress,
3879
- chainId: sessionData.chainId,
3880
- siwe: sessionData.siwe,
3881
- signature: sessionData.signature
3882
- });
3883
- const exactRecap = verified.verifiedRecap;
3914
+ const verified = (() => {
3915
+ try {
3916
+ return this.wasmBindings.validatePersistedSession({
3917
+ delegationHeader: sessionData.delegationHeader,
3918
+ delegationCid: sessionData.delegationCid,
3919
+ spaceId: sessionData.spaceId,
3920
+ jwk: stagedJwk,
3921
+ address: restoredAddress,
3922
+ chainId: sessionData.chainId,
3923
+ siwe: sessionData.siwe,
3924
+ signature: sessionData.signature
3925
+ });
3926
+ } catch (error) {
3927
+ throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
3928
+ }
3929
+ })();
3930
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
3884
3931
  if (!Array.isArray(exactRecap) || !exactRecap.every(
3885
3932
  (entry) => entry !== null && typeof entry === "object" && typeof entry.service === "string" && typeof entry.space === "string" && typeof entry.path === "string" && Array.isArray(entry.actions) && entry.actions.every((action) => typeof action === "string") && Array.isArray(entry.caveats) && entry.caveats.every(
3886
3933
  (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
@@ -3900,7 +3947,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3900
3947
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3901
3948
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3902
3949
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
3903
- throw new Error("Persisted session expiry does not match its signed SIWE authority.");
3950
+ throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
3904
3951
  }
3905
3952
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
3906
3953
  }
@@ -3997,7 +4044,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3997
4044
  operations = recap.flatMap((entry) => {
3998
4045
  const service = this.invocationServiceName(entry.service);
3999
4046
  return entry.actions.map((action) => ({
4000
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
4047
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
4001
4048
  service,
4002
4049
  path: entry.path,
4003
4050
  action,
@@ -4093,7 +4140,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4093
4140
  const delegations = input.recap.map((entry) => ({
4094
4141
  cid: session.delegationCid,
4095
4142
  delegateDID: session.verificationMethod,
4096
- spaceId: entry.space,
4143
+ spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
4097
4144
  path: entry.path,
4098
4145
  actions: [...entry.actions],
4099
4146
  caveats: cloneRecapCaveats(entry.caveats),
@@ -4783,16 +4830,11 @@ var _TinyCloudNode = class _TinyCloudNode {
4783
4830
  );
4784
4831
  graph.assertActive();
4785
4832
  if (!response.ok) {
4786
- let permissionHint;
4787
- if (response.status === 401 || response.status === 403) {
4788
- try {
4789
- const body = await response.json();
4790
- const record = typeof body === "object" && body !== null ? body : void 0;
4791
- const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
4792
- permissionHint = (0, import_sdk_services.parsePermissionHint)(record?.permissionHint) ?? (0, import_sdk_services.parsePermissionHint)(nested?.permissionHint);
4793
- } catch {
4794
- }
4795
- }
4833
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services.parsePermissionHint)({
4834
+ service: "tinycloud.encryption",
4835
+ path: networkId,
4836
+ actions: ["tinycloud.encryption/decrypt"]
4837
+ }) : void 0;
4796
4838
  throw new import_sdk_core8.DecryptTransportResponseError(response.status, permissionHint);
4797
4839
  }
4798
4840
  return await response.json();
@@ -5231,13 +5273,20 @@ var _TinyCloudNode = class _TinyCloudNode {
5231
5273
  const session = this.currentTinyCloudSession();
5232
5274
  const serviceSession = this._serviceContext?.session;
5233
5275
  if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
5276
+ const idempotencyBytes = new TextEncoder().encode(input.idempotencyKey);
5277
+ if (idempotencyBytes.byteLength === 0 || idempotencyBytes.byteLength > 512) {
5278
+ throw new Error("Share delivery idempotency key is invalid");
5279
+ }
5280
+ const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
5234
5281
  const body = {
5235
5282
  envelope: input.envelope,
5283
+ sealedEnvelope: input.sealedEnvelope,
5284
+ envelopeKey: input.envelopeKey,
5236
5285
  shareCid: input.shareCid,
5237
5286
  recipientEmail: input.recipientEmail,
5238
5287
  shareUrl: input.shareUrl,
5239
5288
  documentName: input.documentName,
5240
- jti: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
5289
+ jti: base64UrlEncode(jtiDigest.slice(0, 16)),
5241
5290
  expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
5242
5291
  };
5243
5292
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core8.canonicalizeEncryptionJson)(body)))));
@@ -5720,7 +5769,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5720
5769
  continue;
5721
5770
  }
5722
5771
  let space;
5723
- if (service !== "tinycloud.encryption") {
5772
+ if (service !== "tinycloud.encryption" || operation.resource === void 0) {
5724
5773
  if (typeof operation.spaceId !== "string") continue;
5725
5774
  try {
5726
5775
  space = this.resolvePermissionSpace(operation.spaceId, session);
@@ -6265,11 +6314,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6265
6314
  );
6266
6315
  return { delegation: delegation2, prompted: true };
6267
6316
  }
6268
- const granted = (0, import_sdk_core8.parseRecapCapabilities)(
6269
- (siwe) => this.parseRecapWithCaveats(siwe),
6270
- session.siwe
6271
- );
6272
- const { subset, missing } = (0, import_sdk_core8.isCapabilitySubset)(expandedEntries, granted);
6317
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
6318
+ const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
6273
6319
  if (!subset) {
6274
6320
  const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
6275
6321
  const runtimeGrant = this.findGrantForOperations(
@@ -6589,7 +6635,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6589
6635
  return abilities;
6590
6636
  }
6591
6637
  isEncryptionPermissionEntry(entry) {
6592
- return entry.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE && entry.path.startsWith("urn:tinycloud:encryption:");
6638
+ return entry.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
6593
6639
  }
6594
6640
  permissionsToRawAbilities(entries) {
6595
6641
  const rawAbilities = {};
@@ -6613,7 +6659,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6613
6659
  return entries.flatMap((entry) => {
6614
6660
  const service = this.shortServiceName(entry.service);
6615
6661
  return entry.actions.map((action) => ({
6616
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
6662
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6617
6663
  service,
6618
6664
  path: entry.path,
6619
6665
  action,
@@ -6621,13 +6667,21 @@ var _TinyCloudNode = class _TinyCloudNode {
6621
6667
  }));
6622
6668
  });
6623
6669
  }
6670
+ /** Raw network authority cannot be covered by a nested encryption space. */
6671
+ signedCapabilitySubset(requested, granted) {
6672
+ const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
6673
+ const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
6674
+ const missing = requested.filter((entry) => {
6675
+ const raw = this.isEncryptionPermissionEntry(entry);
6676
+ const comparable = raw ? { ...entry, space: "encryption" } : entry;
6677
+ return !(0, import_sdk_core8.isCapabilitySubset)([comparable], raw ? rawGranted : spaceGranted).subset;
6678
+ });
6679
+ return { subset: missing.length === 0, missing };
6680
+ }
6624
6681
  sessionCoversPermissionEntries(session, entries) {
6625
6682
  try {
6626
- const granted = (0, import_sdk_core8.parseRecapCapabilities)(
6627
- (siwe) => this.parseRecapWithCaveats(siwe),
6628
- session.siwe
6629
- );
6630
- return (0, import_sdk_core8.isCapabilitySubset)(entries, granted).subset;
6683
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
6684
+ return this.signedCapabilitySubset(entries, granted).subset;
6631
6685
  } catch {
6632
6686
  return false;
6633
6687
  }
@@ -6637,7 +6691,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6637
6691
  const spaceId = this.resolvePermissionSpace(entry.space, session);
6638
6692
  const service = this.shortServiceName(entry.service);
6639
6693
  return entry.actions.map((action) => ({
6640
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
6694
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6641
6695
  service,
6642
6696
  path: entry.path,
6643
6697
  action,
@@ -6726,7 +6780,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6726
6780
  return resources.flatMap((resource) => {
6727
6781
  const service = this.invocationServiceName(resource.service);
6728
6782
  return resource.actions.map((action) => ({
6729
- ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
6783
+ ...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
6730
6784
  service,
6731
6785
  path: resource.path,
6732
6786
  action,
@@ -6787,7 +6841,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6787
6841
  };
6788
6842
  for (const resource of resources) {
6789
6843
  const service = this.invocationServiceName(resource.service);
6790
- if (this.isEncryptionNetworkOperation(service, resource.path)) {
6844
+ if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
6791
6845
  rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
6792
6846
  addActions(rawAbilities[resource.path], resource.actions);
6793
6847
  continue;
@@ -6856,21 +6910,15 @@ var _TinyCloudNode = class _TinyCloudNode {
6856
6910
  }
6857
6911
  return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
6858
6912
  }
6859
- // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. The embedded
6860
- // EIP-155 address is case-insensitive, but the CLI canonicalizes it to
6861
- // lowercase when building a space URI while stored runtime delegations keep
6862
- // the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
6863
- // an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
6864
- // segment and leave everything else (crucially the case-sensitive space NAME)
6865
- // byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
6913
+ // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. Compare
6914
+ // only the embedded EIP-155 address without case sensitivity; chain, name,
6915
+ // and all non-PKH space identifiers remain byte-exact.
6866
6916
  spaceIdsEqual(a, b) {
6867
6917
  return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
6868
6918
  }
6869
6919
  normalizeSpaceAddress(space) {
6870
- return space.replace(
6871
- /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
6872
- (_match, prefix, addr) => prefix + addr.toLowerCase()
6873
- );
6920
+ const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
6921
+ return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
6874
6922
  }
6875
6923
  actionContains(grantedAction, requestedAction) {
6876
6924
  if (grantedAction === requestedAction) {
@@ -6887,10 +6935,11 @@ var _TinyCloudNode = class _TinyCloudNode {
6887
6935
  }
6888
6936
  /** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
6889
6937
  parseRecapWithCaveats(siwe) {
6890
- return this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe) ?? this.wasmBindings.parseRecapFromSiwe(siwe);
6938
+ const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
6939
+ return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
6891
6940
  }
6892
- isEncryptionNetworkOperation(service, path) {
6893
- return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
6941
+ isEncryptionNetworkOperation(service, path, space) {
6942
+ return service === "encryption" && (space === void 0 || space === "encryption") && path.startsWith("urn:tinycloud:encryption:");
6894
6943
  }
6895
6944
  operationFromInvokeAnyEntry(entry) {
6896
6945
  const service = this.invocationServiceName(entry.service);
@@ -6903,7 +6952,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6903
6952
  ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
6904
6953
  };
6905
6954
  }
6906
- if (this.isEncryptionNetworkOperation(service, entry.path)) {
6955
+ if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
6907
6956
  return {
6908
6957
  resource: entry.path,
6909
6958
  service,
@@ -7188,8 +7237,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7188
7237
  const jwk = mySession.jwk;
7189
7238
  const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
7190
7239
  const now = /* @__PURE__ */ new Date();
7191
- const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
7192
- const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
7240
+ const expirationTime = delegation.expiry;
7193
7241
  const prepared = this.wasmBindings.prepareSession({
7194
7242
  abilities,
7195
7243
  address: this.wasmBindings.ensureEip55(mySession.address),
@@ -7293,8 +7341,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7293
7341
  );
7294
7342
  }
7295
7343
  const now = /* @__PURE__ */ new Date();
7296
- const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
7297
- const requestedExpiry = new Date(now.getTime() + expiryMs);
7344
+ const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
7298
7345
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
7299
7346
  const abilities = {};
7300
7347
  const rawAbilities = {};
@@ -7750,6 +7797,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
7750
7797
  DuckDbService,
7751
7798
  FileSessionStorage,
7752
7799
  IdentityParseError,
7800
+ InvalidRestoredSessionError,
7753
7801
  KVService,
7754
7802
  ManifestValidationError,
7755
7803
  MemorySessionStorage,