@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/index.cjs CHANGED
@@ -17060,6 +17060,7 @@ __export(index_exports, {
17060
17060
  FileSessionStorage: () => FileSessionStorage,
17061
17061
  HooksService: () => import_sdk_core18.HooksService,
17062
17062
  IdentityParseError: () => import_sdk_core11.IdentityParseError,
17063
+ InvalidRestoredSessionError: () => InvalidRestoredSessionError,
17063
17064
  KVService: () => import_sdk_core13.KVService,
17064
17065
  ManifestValidationError: () => import_sdk_core12.ManifestValidationError,
17065
17066
  MemorySessionStorage: () => MemorySessionStorage,
@@ -17139,6 +17140,7 @@ __export(index_exports, {
17139
17140
  expandActionShortNames: () => import_sdk_core12.expandActionShortNames,
17140
17141
  expandPermissionEntries: () => import_sdk_core12.expandPermissionEntries,
17141
17142
  expandPermissionEntry: () => import_sdk_core12.expandPermissionEntry,
17143
+ extractSiweExpiration: () => extractSiweExpiration,
17142
17144
  generateRandomReceiverKey: () => import_sdk_core17.generateRandomReceiverKey,
17143
17145
  getTinyCloudDebugLogs: () => import_sdk_core11.getTinyCloudDebugLogs,
17144
17146
  grantAuthRequest: () => grantAuthRequest,
@@ -19312,6 +19314,22 @@ function cloneRecapCaveats(caveats) {
19312
19314
  throw new Error("Verified persisted session ReCap contains invalid caveats.");
19313
19315
  }
19314
19316
  }
19317
+ function jsonFromVerifier(value) {
19318
+ if (value === void 0) return null;
19319
+ if (Array.isArray(value)) return value.map(jsonFromVerifier);
19320
+ if (value instanceof Map) {
19321
+ const entries = [...value.entries()];
19322
+ if (!entries.every(([key2]) => typeof key2 === "string")) return value;
19323
+ return Object.fromEntries(entries.map(([key2, nested]) => [key2, jsonFromVerifier(nested)]));
19324
+ }
19325
+ if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
19326
+ return Object.fromEntries(Object.entries(value).map(([key2, nested]) => [key2, jsonFromVerifier(nested)]));
19327
+ }
19328
+ return value;
19329
+ }
19330
+ function verifierRecapEntries(entries) {
19331
+ return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
19332
+ }
19315
19333
  var ServiceGraphLifetime = class {
19316
19334
  constructor(invokeFn, invokeAnyFn, fetchFn) {
19317
19335
  this.invokeFn = invokeFn;
@@ -19384,18 +19402,23 @@ var UnsupportedSessionRestoreError = class extends Error {
19384
19402
  this.name = "UnsupportedSessionRestoreError";
19385
19403
  }
19386
19404
  };
19405
+ var InvalidRestoredSessionError = class extends Error {
19406
+ constructor(message = "Persisted session authority is invalid or expired.") {
19407
+ super(message);
19408
+ this.code = "AUTH_EXPIRED";
19409
+ this.name = "InvalidRestoredSessionError";
19410
+ }
19411
+ };
19387
19412
  function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
19388
19413
  if (typeof persistedVerificationMethod !== "string") return void 0;
19389
19414
  const principal = canonicalVerificationMethod.split("#", 1)[0];
19390
19415
  return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
19391
19416
  }
19392
19417
  function persistedExpiry(value) {
19393
- if (typeof value !== "string") {
19394
- throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
19395
- }
19418
+ if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
19396
19419
  const expiry = new Date(value);
19397
19420
  if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
19398
- throw new Error("Persisted session expiry is invalid or expired.");
19421
+ throw new InvalidRestoredSessionError();
19399
19422
  }
19400
19423
  return expiry;
19401
19424
  }
@@ -19874,6 +19897,29 @@ var _TinyCloudNode = class _TinyCloudNode {
19874
19897
  const authHosts = this.auth?.hosts ?? [];
19875
19898
  return authHosts.length > 0 ? authHosts : [this.config.host];
19876
19899
  }
19900
+ /** The registry-resolved Node endpoint and the identity returned by that exact Node. */
19901
+ async activeNodeIdentity() {
19902
+ const host = this.hosts[0];
19903
+ if (host === void 0) throw new Error("TinyCloud node host has not been resolved");
19904
+ const parsed = new URL(host);
19905
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) || parsed.origin !== host) {
19906
+ throw new Error("TinyCloud node host is not a canonical origin");
19907
+ }
19908
+ const nodeDid = await this.fetchNodeId();
19909
+ if (!nodeDid.startsWith("did:key:z")) throw new Error("TinyCloud node identity is not a did:key");
19910
+ return Object.freeze({ origin: parsed.origin, nodeDid });
19911
+ }
19912
+ /** Publish the active owner Node as a session-signed registry record. */
19913
+ async publishActiveNodeLocation(registryUrl, fetchFn = globalThis.fetch.bind(globalThis)) {
19914
+ const node = await this.activeNodeIdentity();
19915
+ return (0, import_sdk_core7.publishLocationRecord)({
19916
+ registryUrl,
19917
+ subject: this.credentialHolderDid,
19918
+ nodeOrigin: node.origin,
19919
+ signer: { type: "did:key", signBytes: (bytes) => this.signSessionBytes(bytes) },
19920
+ fetch: fetchFn
19921
+ });
19922
+ }
19877
19923
  /**
19878
19924
  * Get the primary identity DID for this user.
19879
19925
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -19915,16 +19961,26 @@ var _TinyCloudNode = class _TinyCloudNode {
19915
19961
  */
19916
19962
  getVerifiedSessionCapabilities() {
19917
19963
  const session = this.currentTinyCloudSession();
19918
- if (!session || !session.siwe) return [];
19919
- return (0, import_sdk_core7.parseRecapCapabilities)(
19920
- (siwe) => this.parseRecapWithCaveats(siwe),
19921
- session.siwe
19922
- ).map((entry) => entry.service === "tinycloud.encryption" ? {
19923
- service: entry.service,
19924
- path: entry.path,
19925
- actions: [...entry.actions],
19926
- ...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
19927
- } : entry);
19964
+ return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
19965
+ }
19966
+ /** Normalize signed capabilities without collapsing nested encryption into raw authority. */
19967
+ projectSignedRecapCapabilities(siwe) {
19968
+ const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
19969
+ const normalized = (0, import_sdk_core7.parseRecapCapabilities)(() => [entry], siwe)[0];
19970
+ if (normalized.service !== "tinycloud.encryption") return normalized;
19971
+ if (!this.isEncryptionNetworkOperation("encryption", entry.path, entry.space)) {
19972
+ return { ...normalized, space: entry.space };
19973
+ }
19974
+ return {
19975
+ service: normalized.service,
19976
+ path: normalized.path,
19977
+ actions: normalized.actions,
19978
+ ...normalized.caveats === void 0 ? {} : { caveats: normalized.caveats }
19979
+ };
19980
+ });
19981
+ return projected.sort(
19982
+ (left, right) => (left.space ?? "").localeCompare(right.space ?? "") || left.service.localeCompare(right.service) || left.path.localeCompare(right.path)
19983
+ );
19928
19984
  }
19929
19985
  /**
19930
19986
  * Get the Ethereum address for this user.
@@ -20379,9 +20435,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20379
20435
  * We must keep the full owner-scoped URI so a synthetic primary grant can
20380
20436
  * never cover an operation on a different owner's space.
20381
20437
  *
20382
- * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
20383
- * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
20384
- * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
20438
+ * Only top-level encryption network entries (`space: "encryption"` or
20439
+ * absent) become `resource` ops. A network-shaped path nested under a
20440
+ * data space remains a `spaceId` op, not raw network authority.
20385
20441
  * One op per action.
20386
20442
  *
20387
20443
  * Returns `[]` for session-only / restored-without-siwe modes and for any
@@ -20402,7 +20458,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20402
20458
  operations = entries.flatMap((entry) => {
20403
20459
  const service = this.invocationServiceName(entry.service);
20404
20460
  return entry.actions.map((action) => ({
20405
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
20461
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
20406
20462
  service,
20407
20463
  path: entry.path,
20408
20464
  action,
@@ -20564,7 +20620,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20564
20620
  return;
20565
20621
  } catch (error) {
20566
20622
  const message = error instanceof Error ? error.message : String(error);
20567
- if (/Unauthorized Action|\b401\b/.test(message)) {
20623
+ if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
20568
20624
  console.warn(
20569
20625
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
20570
20626
  error
@@ -20814,9 +20870,14 @@ var _TinyCloudNode = class _TinyCloudNode {
20814
20870
  */
20815
20871
  async restoreSession(sessionData) {
20816
20872
  await this.wasmBindings.ensureInitialized?.();
20817
- const restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
20873
+ let restoredJwk;
20874
+ try {
20875
+ restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
20876
+ } catch {
20877
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
20878
+ }
20818
20879
  if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
20819
- throw new Error("Persisted session chain ID must be a positive safe integer.");
20880
+ throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
20820
20881
  }
20821
20882
  const stagedManager = this.wasmBindings.createSessionManager();
20822
20883
  const stagedReplace = stagedManager.replaceSessionKey;
@@ -20828,32 +20889,31 @@ var _TinyCloudNode = class _TinyCloudNode {
20828
20889
  let stagedKeyId;
20829
20890
  let stagedJwk;
20830
20891
  let canonicalVerificationMethod;
20892
+ const keyIds = liveKeys.call(this.sessionManager);
20893
+ if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
20894
+ throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
20895
+ }
20896
+ for (const keyId of keyIds) {
20897
+ if (keyId === this.sessionKeyId) continue;
20898
+ const jwk = this.sessionManager.jwk(keyId);
20899
+ if (!jwk) throw new Error("missing live session key");
20900
+ stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
20901
+ }
20831
20902
  try {
20832
- const keyIds = liveKeys.call(this.sessionManager);
20833
- if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
20834
- throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
20835
- }
20836
- for (const keyId of keyIds) {
20837
- if (keyId === this.sessionKeyId) continue;
20838
- const jwk = this.sessionManager.jwk(keyId);
20839
- if (!jwk) throw new Error("missing live session key");
20840
- stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
20841
- }
20842
20903
  stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
20843
- const stagedJwkJson = stagedManager.jwk(stagedKeyId);
20844
- if (!stagedJwkJson) throw new Error("missing restored session key");
20845
- stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
20846
- canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
20847
- } catch (error) {
20848
- if (error instanceof UnsupportedSessionRestoreError) throw error;
20849
- throw new Error("Persisted session has an invalid private Ed25519 session key.");
20904
+ } catch {
20905
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
20850
20906
  }
20907
+ const stagedJwkJson = stagedManager.jwk(stagedKeyId);
20908
+ if (!stagedJwkJson) throw new Error("missing restored session key");
20909
+ stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
20910
+ canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
20851
20911
  const restoredVerificationMethod = canonicalRestoredVerificationMethod(
20852
20912
  canonicalVerificationMethod,
20853
20913
  sessionData.verificationMethod
20854
20914
  );
20855
20915
  if (!restoredVerificationMethod) {
20856
- throw new Error(
20916
+ throw new InvalidRestoredSessionError(
20857
20917
  "Persisted session verification method does not match its private Ed25519 session key."
20858
20918
  );
20859
20919
  }
@@ -20865,26 +20925,37 @@ var _TinyCloudNode = class _TinyCloudNode {
20865
20925
  ];
20866
20926
  const hasPersistedProof = proofValues.every((value) => value !== void 0);
20867
20927
  if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
20868
- throw new Error("Persisted session authority metadata is incomplete.");
20928
+ throw new InvalidRestoredSessionError("Persisted session authority metadata is incomplete.");
20929
+ }
20930
+ let restoredAddress;
20931
+ try {
20932
+ restoredAddress = hasPersistedProof ? (0, import_sdk_core7.canonicalizeAddress)(sessionData.address) : void 0;
20933
+ } catch {
20934
+ throw new InvalidRestoredSessionError("Persisted session has an invalid wallet address.");
20869
20935
  }
20870
- const restoredAddress = hasPersistedProof ? (0, import_sdk_core7.canonicalizeAddress)(sessionData.address) : void 0;
20871
20936
  let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
20872
20937
  let stagedRecap = [];
20873
20938
  if (hasPersistedProof) {
20874
20939
  if (typeof this.wasmBindings.validatePersistedSession !== "function") {
20875
20940
  throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
20876
20941
  }
20877
- const verified = this.wasmBindings.validatePersistedSession({
20878
- delegationHeader: sessionData.delegationHeader,
20879
- delegationCid: sessionData.delegationCid,
20880
- spaceId: sessionData.spaceId,
20881
- jwk: stagedJwk,
20882
- address: restoredAddress,
20883
- chainId: sessionData.chainId,
20884
- siwe: sessionData.siwe,
20885
- signature: sessionData.signature
20886
- });
20887
- const exactRecap = verified.verifiedRecap;
20942
+ const verified = (() => {
20943
+ try {
20944
+ return this.wasmBindings.validatePersistedSession({
20945
+ delegationHeader: sessionData.delegationHeader,
20946
+ delegationCid: sessionData.delegationCid,
20947
+ spaceId: sessionData.spaceId,
20948
+ jwk: stagedJwk,
20949
+ address: restoredAddress,
20950
+ chainId: sessionData.chainId,
20951
+ siwe: sessionData.siwe,
20952
+ signature: sessionData.signature
20953
+ });
20954
+ } catch (error) {
20955
+ throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
20956
+ }
20957
+ })();
20958
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
20888
20959
  if (!Array.isArray(exactRecap) || !exactRecap.every(
20889
20960
  (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(
20890
20961
  (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
@@ -20904,7 +20975,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20904
20975
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
20905
20976
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
20906
20977
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
20907
- throw new Error("Persisted session expiry does not match its signed SIWE authority.");
20978
+ throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
20908
20979
  }
20909
20980
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
20910
20981
  }
@@ -21001,7 +21072,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21001
21072
  operations = recap.flatMap((entry) => {
21002
21073
  const service = this.invocationServiceName(entry.service);
21003
21074
  return entry.actions.map((action) => ({
21004
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
21075
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
21005
21076
  service,
21006
21077
  path: entry.path,
21007
21078
  action,
@@ -21097,7 +21168,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21097
21168
  const delegations = input.recap.map((entry) => ({
21098
21169
  cid: session.delegationCid,
21099
21170
  delegateDID: session.verificationMethod,
21100
- spaceId: entry.space,
21171
+ spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
21101
21172
  path: entry.path,
21102
21173
  actions: [...entry.actions],
21103
21174
  caveats: cloneRecapCaveats(entry.caveats),
@@ -21787,16 +21858,11 @@ var _TinyCloudNode = class _TinyCloudNode {
21787
21858
  );
21788
21859
  graph.assertActive();
21789
21860
  if (!response.ok) {
21790
- let permissionHint;
21791
- if (response.status === 401 || response.status === 403) {
21792
- try {
21793
- const body = await response.json();
21794
- const record = typeof body === "object" && body !== null ? body : void 0;
21795
- const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
21796
- permissionHint = (0, import_sdk_services.parsePermissionHint)(record?.permissionHint) ?? (0, import_sdk_services.parsePermissionHint)(nested?.permissionHint);
21797
- } catch {
21798
- }
21799
- }
21861
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services.parsePermissionHint)({
21862
+ service: "tinycloud.encryption",
21863
+ path: networkId,
21864
+ actions: ["tinycloud.encryption/decrypt"]
21865
+ }) : void 0;
21800
21866
  throw new import_sdk_core7.DecryptTransportResponseError(response.status, permissionHint);
21801
21867
  }
21802
21868
  return await response.json();
@@ -22230,16 +22296,16 @@ var _TinyCloudNode = class _TinyCloudNode {
22230
22296
  fetch: globalThis.fetch.bind(globalThis)
22231
22297
  });
22232
22298
  }
22233
- /** @deprecated Policy/v2 delivery transport is retired. Use authorizeShareDeliveryV3. */
22234
- async authorizeShareDelivery(input) {
22235
- void input;
22236
- throw new Error("Policy/v2 share delivery is retired; use authorizeShareDeliveryV3");
22237
- }
22238
22299
  /** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
22239
22300
  async authorizeShareDeliveryV3(input) {
22240
22301
  const session = this.currentTinyCloudSession();
22241
22302
  const serviceSession = this._serviceContext?.session;
22242
22303
  if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
22304
+ const idempotencyBytes = new TextEncoder().encode(input.idempotencyKey);
22305
+ if (idempotencyBytes.byteLength === 0 || idempotencyBytes.byteLength > 512) {
22306
+ throw new Error("Share delivery idempotency key is invalid");
22307
+ }
22308
+ const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
22243
22309
  const body = {
22244
22310
  envelope: input.envelope,
22245
22311
  sealedEnvelope: input.sealedEnvelope,
@@ -22248,7 +22314,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22248
22314
  recipientEmail: input.recipientEmail,
22249
22315
  shareUrl: input.shareUrl,
22250
22316
  documentName: input.documentName,
22251
- jti: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
22317
+ jti: base64UrlEncode(jtiDigest.slice(0, 16)),
22252
22318
  expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
22253
22319
  };
22254
22320
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core7.canonicalizeEncryptionJson)(body)))));
@@ -22263,9 +22329,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22263
22329
  const verified = (0, import_sdk_core7.validateShareDeliveryAuthorizationV3Bytes)(new Uint8Array(await response.arrayBuffer()), {
22264
22330
  request,
22265
22331
  senderKeyDid: this.credentialHolderDid,
22266
- credentialsAudience: input.credentialsAudience
22332
+ deliveryAudience: input.deliveryAudience
22267
22333
  });
22268
- void input.nodeProof;
22269
22334
  const requestDigest = new Uint8Array(await crypto.subtle.digest(
22270
22335
  "SHA-256",
22271
22336
  new TextEncoder().encode(`${import_sdk_core7.CREDENTIAL_INVITATION_REQUEST_DOMAIN}${(0, import_sdk_core7.canonicalizeEncryptionJson)(verified.request)}`)
@@ -22732,7 +22797,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22732
22797
  continue;
22733
22798
  }
22734
22799
  let space;
22735
- if (service !== "tinycloud.encryption") {
22800
+ if (service !== "tinycloud.encryption" || operation.resource === void 0) {
22736
22801
  if (typeof operation.spaceId !== "string") continue;
22737
22802
  try {
22738
22803
  space = this.resolvePermissionSpace(operation.spaceId, session);
@@ -23277,11 +23342,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23277
23342
  );
23278
23343
  return { delegation: delegation2, prompted: true };
23279
23344
  }
23280
- const granted = (0, import_sdk_core7.parseRecapCapabilities)(
23281
- (siwe) => this.parseRecapWithCaveats(siwe),
23282
- session.siwe
23283
- );
23284
- const { subset, missing } = (0, import_sdk_core7.isCapabilitySubset)(expandedEntries, granted);
23345
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
23346
+ const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
23285
23347
  if (!subset) {
23286
23348
  const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
23287
23349
  const runtimeGrant = this.findGrantForOperations(
@@ -23601,7 +23663,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23601
23663
  return abilities;
23602
23664
  }
23603
23665
  isEncryptionPermissionEntry(entry) {
23604
- return entry.service === import_sdk_core7.ENCRYPTION_PERMISSION_SERVICE && entry.path.startsWith("urn:tinycloud:encryption:");
23666
+ return entry.service === import_sdk_core7.ENCRYPTION_PERMISSION_SERVICE && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
23605
23667
  }
23606
23668
  permissionsToRawAbilities(entries) {
23607
23669
  const rawAbilities = {};
@@ -23625,7 +23687,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23625
23687
  return entries.flatMap((entry) => {
23626
23688
  const service = this.shortServiceName(entry.service);
23627
23689
  return entry.actions.map((action) => ({
23628
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
23690
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
23629
23691
  service,
23630
23692
  path: entry.path,
23631
23693
  action,
@@ -23633,13 +23695,21 @@ var _TinyCloudNode = class _TinyCloudNode {
23633
23695
  }));
23634
23696
  });
23635
23697
  }
23698
+ /** Raw network authority cannot be covered by a nested encryption space. */
23699
+ signedCapabilitySubset(requested, granted) {
23700
+ const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
23701
+ const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
23702
+ const missing = requested.filter((entry) => {
23703
+ const raw = this.isEncryptionPermissionEntry(entry);
23704
+ const comparable = raw ? { ...entry, space: "encryption" } : entry;
23705
+ return !(0, import_sdk_core7.isCapabilitySubset)([comparable], raw ? rawGranted : spaceGranted).subset;
23706
+ });
23707
+ return { subset: missing.length === 0, missing };
23708
+ }
23636
23709
  sessionCoversPermissionEntries(session, entries) {
23637
23710
  try {
23638
- const granted = (0, import_sdk_core7.parseRecapCapabilities)(
23639
- (siwe) => this.parseRecapWithCaveats(siwe),
23640
- session.siwe
23641
- );
23642
- return (0, import_sdk_core7.isCapabilitySubset)(entries, granted).subset;
23711
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
23712
+ return this.signedCapabilitySubset(entries, granted).subset;
23643
23713
  } catch {
23644
23714
  return false;
23645
23715
  }
@@ -23649,7 +23719,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23649
23719
  const spaceId = this.resolvePermissionSpace(entry.space, session);
23650
23720
  const service = this.shortServiceName(entry.service);
23651
23721
  return entry.actions.map((action) => ({
23652
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
23722
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
23653
23723
  service,
23654
23724
  path: entry.path,
23655
23725
  action,
@@ -23738,7 +23808,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23738
23808
  return resources.flatMap((resource) => {
23739
23809
  const service = this.invocationServiceName(resource.service);
23740
23810
  return resource.actions.map((action) => ({
23741
- ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
23811
+ ...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
23742
23812
  service,
23743
23813
  path: resource.path,
23744
23814
  action,
@@ -23799,7 +23869,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23799
23869
  };
23800
23870
  for (const resource of resources) {
23801
23871
  const service = this.invocationServiceName(resource.service);
23802
- if (this.isEncryptionNetworkOperation(service, resource.path)) {
23872
+ if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
23803
23873
  rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
23804
23874
  addActions(rawAbilities[resource.path], resource.actions);
23805
23875
  continue;
@@ -23868,21 +23938,15 @@ var _TinyCloudNode = class _TinyCloudNode {
23868
23938
  }
23869
23939
  return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
23870
23940
  }
23871
- // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. The embedded
23872
- // EIP-155 address is case-insensitive, but the CLI canonicalizes it to
23873
- // lowercase when building a space URI while stored runtime delegations keep
23874
- // the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
23875
- // an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
23876
- // segment and leave everything else (crucially the case-sensitive space NAME)
23877
- // byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
23941
+ // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. Compare
23942
+ // only the embedded EIP-155 address without case sensitivity; chain, name,
23943
+ // and all non-PKH space identifiers remain byte-exact.
23878
23944
  spaceIdsEqual(a, b) {
23879
23945
  return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
23880
23946
  }
23881
23947
  normalizeSpaceAddress(space) {
23882
- return space.replace(
23883
- /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
23884
- (_match, prefix, addr) => prefix + addr.toLowerCase()
23885
- );
23948
+ const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
23949
+ return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
23886
23950
  }
23887
23951
  actionContains(grantedAction, requestedAction) {
23888
23952
  if (grantedAction === requestedAction) {
@@ -23899,10 +23963,11 @@ var _TinyCloudNode = class _TinyCloudNode {
23899
23963
  }
23900
23964
  /** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
23901
23965
  parseRecapWithCaveats(siwe) {
23902
- return this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe) ?? this.wasmBindings.parseRecapFromSiwe(siwe);
23966
+ const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
23967
+ return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
23903
23968
  }
23904
- isEncryptionNetworkOperation(service, path) {
23905
- return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
23969
+ isEncryptionNetworkOperation(service, path, space) {
23970
+ return service === "encryption" && (space === void 0 || space === "encryption") && path.startsWith("urn:tinycloud:encryption:");
23906
23971
  }
23907
23972
  operationFromInvokeAnyEntry(entry) {
23908
23973
  const service = this.invocationServiceName(entry.service);
@@ -23915,7 +23980,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23915
23980
  ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
23916
23981
  };
23917
23982
  }
23918
- if (this.isEncryptionNetworkOperation(service, entry.path)) {
23983
+ if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
23919
23984
  return {
23920
23985
  resource: entry.path,
23921
23986
  service,
@@ -24200,8 +24265,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24200
24265
  const jwk = mySession.jwk;
24201
24266
  const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
24202
24267
  const now = /* @__PURE__ */ new Date();
24203
- const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
24204
- const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
24268
+ const expirationTime = delegation.expiry;
24205
24269
  const prepared = this.wasmBindings.prepareSession({
24206
24270
  abilities,
24207
24271
  address: this.wasmBindings.ensureEip55(mySession.address),
@@ -24305,8 +24369,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24305
24369
  );
24306
24370
  }
24307
24371
  const now = /* @__PURE__ */ new Date();
24308
- const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
24309
- const requestedExpiry = new Date(now.getTime() + expiryMs);
24372
+ const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
24310
24373
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
24311
24374
  const abilities = {};
24312
24375
  const rawAbilities = {};
@@ -24979,6 +25042,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
24979
25042
  FileSessionStorage,
24980
25043
  HooksService,
24981
25044
  IdentityParseError,
25045
+ InvalidRestoredSessionError,
24982
25046
  KVService,
24983
25047
  ManifestValidationError,
24984
25048
  MemorySessionStorage,
@@ -25058,6 +25122,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
25058
25122
  expandActionShortNames,
25059
25123
  expandPermissionEntries,
25060
25124
  expandPermissionEntry,
25125
+ extractSiweExpiration,
25061
25126
  generateRandomReceiverKey,
25062
25127
  getTinyCloudDebugLogs,
25063
25128
  grantAuthRequest,