@tinycloud/node-sdk 3.0.0-beta.8 → 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
  }
@@ -2847,6 +2869,29 @@ var _TinyCloudNode = class _TinyCloudNode {
2847
2869
  const authHosts = this.auth?.hosts ?? [];
2848
2870
  return authHosts.length > 0 ? authHosts : [this.config.host];
2849
2871
  }
2872
+ /** The registry-resolved Node endpoint and the identity returned by that exact Node. */
2873
+ async activeNodeIdentity() {
2874
+ const host = this.hosts[0];
2875
+ if (host === void 0) throw new Error("TinyCloud node host has not been resolved");
2876
+ const parsed = new URL(host);
2877
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) || parsed.origin !== host) {
2878
+ throw new Error("TinyCloud node host is not a canonical origin");
2879
+ }
2880
+ const nodeDid = await this.fetchNodeId();
2881
+ if (!nodeDid.startsWith("did:key:z")) throw new Error("TinyCloud node identity is not a did:key");
2882
+ return Object.freeze({ origin: parsed.origin, nodeDid });
2883
+ }
2884
+ /** Publish the active owner Node as a session-signed registry record. */
2885
+ async publishActiveNodeLocation(registryUrl, fetchFn = globalThis.fetch.bind(globalThis)) {
2886
+ const node = await this.activeNodeIdentity();
2887
+ return (0, import_sdk_core8.publishLocationRecord)({
2888
+ registryUrl,
2889
+ subject: this.credentialHolderDid,
2890
+ nodeOrigin: node.origin,
2891
+ signer: { type: "did:key", signBytes: (bytes) => this.signSessionBytes(bytes) },
2892
+ fetch: fetchFn
2893
+ });
2894
+ }
2850
2895
  /**
2851
2896
  * Get the primary identity DID for this user.
2852
2897
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -2888,16 +2933,26 @@ var _TinyCloudNode = class _TinyCloudNode {
2888
2933
  */
2889
2934
  getVerifiedSessionCapabilities() {
2890
2935
  const session = this.currentTinyCloudSession();
2891
- if (!session || !session.siwe) return [];
2892
- return (0, import_sdk_core8.parseRecapCapabilities)(
2893
- (siwe) => this.parseRecapWithCaveats(siwe),
2894
- session.siwe
2895
- ).map((entry) => entry.service === "tinycloud.encryption" ? {
2896
- service: entry.service,
2897
- path: entry.path,
2898
- actions: [...entry.actions],
2899
- ...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
2900
- } : 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
+ );
2901
2956
  }
2902
2957
  /**
2903
2958
  * Get the Ethereum address for this user.
@@ -3352,9 +3407,9 @@ var _TinyCloudNode = class _TinyCloudNode {
3352
3407
  * We must keep the full owner-scoped URI so a synthetic primary grant can
3353
3408
  * never cover an operation on a different owner's space.
3354
3409
  *
3355
- * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
3356
- * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
3357
- * 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.
3358
3413
  * One op per action.
3359
3414
  *
3360
3415
  * Returns `[]` for session-only / restored-without-siwe modes and for any
@@ -3375,7 +3430,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3375
3430
  operations = entries.flatMap((entry) => {
3376
3431
  const service = this.invocationServiceName(entry.service);
3377
3432
  return entry.actions.map((action) => ({
3378
- ...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 },
3379
3434
  service,
3380
3435
  path: entry.path,
3381
3436
  action,
@@ -3537,7 +3592,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3537
3592
  return;
3538
3593
  } catch (error) {
3539
3594
  const message = error instanceof Error ? error.message : String(error);
3540
- if (/Unauthorized Action|\b401\b/.test(message)) {
3595
+ if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
3541
3596
  console.warn(
3542
3597
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
3543
3598
  error
@@ -3787,9 +3842,14 @@ var _TinyCloudNode = class _TinyCloudNode {
3787
3842
  */
3788
3843
  async restoreSession(sessionData) {
3789
3844
  await this.wasmBindings.ensureInitialized?.();
3790
- 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
+ }
3791
3851
  if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
3792
- 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.");
3793
3853
  }
3794
3854
  const stagedManager = this.wasmBindings.createSessionManager();
3795
3855
  const stagedReplace = stagedManager.replaceSessionKey;
@@ -3801,32 +3861,31 @@ var _TinyCloudNode = class _TinyCloudNode {
3801
3861
  let stagedKeyId;
3802
3862
  let stagedJwk;
3803
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
+ }
3804
3874
  try {
3805
- const keyIds = liveKeys.call(this.sessionManager);
3806
- if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
3807
- throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
3808
- }
3809
- for (const keyId of keyIds) {
3810
- if (keyId === this.sessionKeyId) continue;
3811
- const jwk = this.sessionManager.jwk(keyId);
3812
- if (!jwk) throw new Error("missing live session key");
3813
- stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
3814
- }
3815
3875
  stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
3816
- const stagedJwkJson = stagedManager.jwk(stagedKeyId);
3817
- if (!stagedJwkJson) throw new Error("missing restored session key");
3818
- stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
3819
- canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
3820
- } catch (error) {
3821
- if (error instanceof UnsupportedSessionRestoreError) throw error;
3822
- 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.");
3823
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);
3824
3883
  const restoredVerificationMethod = canonicalRestoredVerificationMethod(
3825
3884
  canonicalVerificationMethod,
3826
3885
  sessionData.verificationMethod
3827
3886
  );
3828
3887
  if (!restoredVerificationMethod) {
3829
- throw new Error(
3888
+ throw new InvalidRestoredSessionError(
3830
3889
  "Persisted session verification method does not match its private Ed25519 session key."
3831
3890
  );
3832
3891
  }
@@ -3838,26 +3897,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3838
3897
  ];
3839
3898
  const hasPersistedProof = proofValues.every((value) => value !== void 0);
3840
3899
  if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
3841
- 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.");
3842
3907
  }
3843
- const restoredAddress = hasPersistedProof ? (0, import_sdk_core8.canonicalizeAddress)(sessionData.address) : void 0;
3844
3908
  let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
3845
3909
  let stagedRecap = [];
3846
3910
  if (hasPersistedProof) {
3847
3911
  if (typeof this.wasmBindings.validatePersistedSession !== "function") {
3848
3912
  throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
3849
3913
  }
3850
- const verified = this.wasmBindings.validatePersistedSession({
3851
- delegationHeader: sessionData.delegationHeader,
3852
- delegationCid: sessionData.delegationCid,
3853
- spaceId: sessionData.spaceId,
3854
- jwk: stagedJwk,
3855
- address: restoredAddress,
3856
- chainId: sessionData.chainId,
3857
- siwe: sessionData.siwe,
3858
- signature: sessionData.signature
3859
- });
3860
- 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;
3861
3931
  if (!Array.isArray(exactRecap) || !exactRecap.every(
3862
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(
3863
3933
  (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
@@ -3877,7 +3947,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3877
3947
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
3878
3948
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
3879
3949
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
3880
- 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.");
3881
3951
  }
3882
3952
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
3883
3953
  }
@@ -3974,7 +4044,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3974
4044
  operations = recap.flatMap((entry) => {
3975
4045
  const service = this.invocationServiceName(entry.service);
3976
4046
  return entry.actions.map((action) => ({
3977
- ...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 },
3978
4048
  service,
3979
4049
  path: entry.path,
3980
4050
  action,
@@ -4070,7 +4140,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4070
4140
  const delegations = input.recap.map((entry) => ({
4071
4141
  cid: session.delegationCid,
4072
4142
  delegateDID: session.verificationMethod,
4073
- spaceId: entry.space,
4143
+ spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
4074
4144
  path: entry.path,
4075
4145
  actions: [...entry.actions],
4076
4146
  caveats: cloneRecapCaveats(entry.caveats),
@@ -4760,16 +4830,11 @@ var _TinyCloudNode = class _TinyCloudNode {
4760
4830
  );
4761
4831
  graph.assertActive();
4762
4832
  if (!response.ok) {
4763
- let permissionHint;
4764
- if (response.status === 401 || response.status === 403) {
4765
- try {
4766
- const body = await response.json();
4767
- const record = typeof body === "object" && body !== null ? body : void 0;
4768
- const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
4769
- permissionHint = (0, import_sdk_services.parsePermissionHint)(record?.permissionHint) ?? (0, import_sdk_services.parsePermissionHint)(nested?.permissionHint);
4770
- } catch {
4771
- }
4772
- }
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;
4773
4838
  throw new import_sdk_core8.DecryptTransportResponseError(response.status, permissionHint);
4774
4839
  }
4775
4840
  return await response.json();
@@ -5203,16 +5268,16 @@ var _TinyCloudNode = class _TinyCloudNode {
5203
5268
  fetch: globalThis.fetch.bind(globalThis)
5204
5269
  });
5205
5270
  }
5206
- /** @deprecated Policy/v2 delivery transport is retired. Use authorizeShareDeliveryV3. */
5207
- async authorizeShareDelivery(input) {
5208
- void input;
5209
- throw new Error("Policy/v2 share delivery is retired; use authorizeShareDeliveryV3");
5210
- }
5211
5271
  /** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
5212
5272
  async authorizeShareDeliveryV3(input) {
5213
5273
  const session = this.currentTinyCloudSession();
5214
5274
  const serviceSession = this._serviceContext?.session;
5215
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));
5216
5281
  const body = {
5217
5282
  envelope: input.envelope,
5218
5283
  sealedEnvelope: input.sealedEnvelope,
@@ -5221,7 +5286,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5221
5286
  recipientEmail: input.recipientEmail,
5222
5287
  shareUrl: input.shareUrl,
5223
5288
  documentName: input.documentName,
5224
- jti: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
5289
+ jti: base64UrlEncode(jtiDigest.slice(0, 16)),
5225
5290
  expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
5226
5291
  };
5227
5292
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core8.canonicalizeEncryptionJson)(body)))));
@@ -5236,9 +5301,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5236
5301
  const verified = (0, import_sdk_core8.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
5237
5302
  request,
5238
5303
  senderKeyDid: this.credentialHolderDid,
5239
- credentialsAudience: input.credentialsAudience
5304
+ deliveryAudience: input.deliveryAudience
5240
5305
  });
5241
- void input.nodeProof;
5242
5306
  const requestDigest = new Uint8Array(await crypto.subtle.digest(
5243
5307
  "SHA-256",
5244
5308
  new TextEncoder().encode(`${import_sdk_core8.CREDENTIAL_INVITATION_REQUEST_DOMAIN}${(0, import_sdk_core8.canonicalizeEncryptionJson)(verified.request)}`)
@@ -5705,7 +5769,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5705
5769
  continue;
5706
5770
  }
5707
5771
  let space;
5708
- if (service !== "tinycloud.encryption") {
5772
+ if (service !== "tinycloud.encryption" || operation.resource === void 0) {
5709
5773
  if (typeof operation.spaceId !== "string") continue;
5710
5774
  try {
5711
5775
  space = this.resolvePermissionSpace(operation.spaceId, session);
@@ -6250,11 +6314,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6250
6314
  );
6251
6315
  return { delegation: delegation2, prompted: true };
6252
6316
  }
6253
- const granted = (0, import_sdk_core8.parseRecapCapabilities)(
6254
- (siwe) => this.parseRecapWithCaveats(siwe),
6255
- session.siwe
6256
- );
6257
- 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);
6258
6319
  if (!subset) {
6259
6320
  const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
6260
6321
  const runtimeGrant = this.findGrantForOperations(
@@ -6574,7 +6635,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6574
6635
  return abilities;
6575
6636
  }
6576
6637
  isEncryptionPermissionEntry(entry) {
6577
- 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);
6578
6639
  }
6579
6640
  permissionsToRawAbilities(entries) {
6580
6641
  const rawAbilities = {};
@@ -6598,7 +6659,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6598
6659
  return entries.flatMap((entry) => {
6599
6660
  const service = this.shortServiceName(entry.service);
6600
6661
  return entry.actions.map((action) => ({
6601
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
6662
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6602
6663
  service,
6603
6664
  path: entry.path,
6604
6665
  action,
@@ -6606,13 +6667,21 @@ var _TinyCloudNode = class _TinyCloudNode {
6606
6667
  }));
6607
6668
  });
6608
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
+ }
6609
6681
  sessionCoversPermissionEntries(session, entries) {
6610
6682
  try {
6611
- const granted = (0, import_sdk_core8.parseRecapCapabilities)(
6612
- (siwe) => this.parseRecapWithCaveats(siwe),
6613
- session.siwe
6614
- );
6615
- return (0, import_sdk_core8.isCapabilitySubset)(entries, granted).subset;
6683
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
6684
+ return this.signedCapabilitySubset(entries, granted).subset;
6616
6685
  } catch {
6617
6686
  return false;
6618
6687
  }
@@ -6622,7 +6691,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6622
6691
  const spaceId = this.resolvePermissionSpace(entry.space, session);
6623
6692
  const service = this.shortServiceName(entry.service);
6624
6693
  return entry.actions.map((action) => ({
6625
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
6694
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
6626
6695
  service,
6627
6696
  path: entry.path,
6628
6697
  action,
@@ -6711,7 +6780,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6711
6780
  return resources.flatMap((resource) => {
6712
6781
  const service = this.invocationServiceName(resource.service);
6713
6782
  return resource.actions.map((action) => ({
6714
- ...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 },
6715
6784
  service,
6716
6785
  path: resource.path,
6717
6786
  action,
@@ -6772,7 +6841,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6772
6841
  };
6773
6842
  for (const resource of resources) {
6774
6843
  const service = this.invocationServiceName(resource.service);
6775
- if (this.isEncryptionNetworkOperation(service, resource.path)) {
6844
+ if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
6776
6845
  rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
6777
6846
  addActions(rawAbilities[resource.path], resource.actions);
6778
6847
  continue;
@@ -6841,21 +6910,15 @@ var _TinyCloudNode = class _TinyCloudNode {
6841
6910
  }
6842
6911
  return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
6843
6912
  }
6844
- // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. The embedded
6845
- // EIP-155 address is case-insensitive, but the CLI canonicalizes it to
6846
- // lowercase when building a space URI while stored runtime delegations keep
6847
- // the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
6848
- // an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
6849
- // segment and leave everything else (crucially the case-sensitive space NAME)
6850
- // 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.
6851
6916
  spaceIdsEqual(a, b) {
6852
6917
  return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
6853
6918
  }
6854
6919
  normalizeSpaceAddress(space) {
6855
- return space.replace(
6856
- /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
6857
- (_match, prefix, addr) => prefix + addr.toLowerCase()
6858
- );
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]}`;
6859
6922
  }
6860
6923
  actionContains(grantedAction, requestedAction) {
6861
6924
  if (grantedAction === requestedAction) {
@@ -6872,10 +6935,11 @@ var _TinyCloudNode = class _TinyCloudNode {
6872
6935
  }
6873
6936
  /** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
6874
6937
  parseRecapWithCaveats(siwe) {
6875
- 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);
6876
6940
  }
6877
- isEncryptionNetworkOperation(service, path) {
6878
- 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:");
6879
6943
  }
6880
6944
  operationFromInvokeAnyEntry(entry) {
6881
6945
  const service = this.invocationServiceName(entry.service);
@@ -6888,7 +6952,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6888
6952
  ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
6889
6953
  };
6890
6954
  }
6891
- if (this.isEncryptionNetworkOperation(service, entry.path)) {
6955
+ if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
6892
6956
  return {
6893
6957
  resource: entry.path,
6894
6958
  service,
@@ -7173,8 +7237,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7173
7237
  const jwk = mySession.jwk;
7174
7238
  const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
7175
7239
  const now = /* @__PURE__ */ new Date();
7176
- const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
7177
- const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
7240
+ const expirationTime = delegation.expiry;
7178
7241
  const prepared = this.wasmBindings.prepareSession({
7179
7242
  abilities,
7180
7243
  address: this.wasmBindings.ensureEip55(mySession.address),
@@ -7278,8 +7341,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7278
7341
  );
7279
7342
  }
7280
7343
  const now = /* @__PURE__ */ new Date();
7281
- const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
7282
- const requestedExpiry = new Date(now.getTime() + expiryMs);
7344
+ const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
7283
7345
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
7284
7346
  const abilities = {};
7285
7347
  const rawAbilities = {};
@@ -7735,6 +7797,7 @@ var import_sdk_core21 = require("@tinycloud/sdk-core");
7735
7797
  DuckDbService,
7736
7798
  FileSessionStorage,
7737
7799
  IdentityParseError,
7800
+ InvalidRestoredSessionError,
7738
7801
  KVService,
7739
7802
  ManifestValidationError,
7740
7803
  MemorySessionStorage,