@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.js CHANGED
@@ -17209,6 +17209,7 @@ import {
17209
17209
  canonicalizeAddress as canonicalizeAddress2,
17210
17210
  pkhDid as pkhDid2,
17211
17211
  resolveTinyCloudHosts as resolveTinyCloudHosts2,
17212
+ publishLocationRecord,
17212
17213
  principalDidEquals as principalDidEquals2,
17213
17214
  parseNetworkId as parseNetworkId2,
17214
17215
  resolveSecretPath as resolveSecretPath2,
@@ -19298,6 +19299,22 @@ function cloneRecapCaveats(caveats) {
19298
19299
  throw new Error("Verified persisted session ReCap contains invalid caveats.");
19299
19300
  }
19300
19301
  }
19302
+ function jsonFromVerifier(value) {
19303
+ if (value === void 0) return null;
19304
+ if (Array.isArray(value)) return value.map(jsonFromVerifier);
19305
+ if (value instanceof Map) {
19306
+ const entries = [...value.entries()];
19307
+ if (!entries.every(([key2]) => typeof key2 === "string")) return value;
19308
+ return Object.fromEntries(entries.map(([key2, nested]) => [key2, jsonFromVerifier(nested)]));
19309
+ }
19310
+ if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
19311
+ return Object.fromEntries(Object.entries(value).map(([key2, nested]) => [key2, jsonFromVerifier(nested)]));
19312
+ }
19313
+ return value;
19314
+ }
19315
+ function verifierRecapEntries(entries) {
19316
+ return entries.map((entry) => Array.isArray(entry.caveats) ? { ...entry, caveats: entry.caveats.map((caveat) => jsonFromVerifier(caveat)) } : entry);
19317
+ }
19301
19318
  var ServiceGraphLifetime = class {
19302
19319
  constructor(invokeFn, invokeAnyFn, fetchFn) {
19303
19320
  this.invokeFn = invokeFn;
@@ -19370,18 +19387,23 @@ var UnsupportedSessionRestoreError = class extends Error {
19370
19387
  this.name = "UnsupportedSessionRestoreError";
19371
19388
  }
19372
19389
  };
19390
+ var InvalidRestoredSessionError = class extends Error {
19391
+ constructor(message = "Persisted session authority is invalid or expired.") {
19392
+ super(message);
19393
+ this.code = "AUTH_EXPIRED";
19394
+ this.name = "InvalidRestoredSessionError";
19395
+ }
19396
+ };
19373
19397
  function canonicalRestoredVerificationMethod(canonicalVerificationMethod, persistedVerificationMethod) {
19374
19398
  if (typeof persistedVerificationMethod !== "string") return void 0;
19375
19399
  const principal = canonicalVerificationMethod.split("#", 1)[0];
19376
19400
  return persistedVerificationMethod === principal || persistedVerificationMethod === canonicalVerificationMethod ? canonicalVerificationMethod : void 0;
19377
19401
  }
19378
19402
  function persistedExpiry(value) {
19379
- if (typeof value !== "string") {
19380
- throw new Error("Persisted session without a SIWE expiration must include expiresAt.");
19381
- }
19403
+ if (typeof value !== "string") throw new InvalidRestoredSessionError("Persisted session must include expiresAt.");
19382
19404
  const expiry = new Date(value);
19383
19405
  if (!Number.isFinite(expiry.getTime()) || expiry.getTime() <= Date.now()) {
19384
- throw new Error("Persisted session expiry is invalid or expired.");
19406
+ throw new InvalidRestoredSessionError();
19385
19407
  }
19386
19408
  return expiry;
19387
19409
  }
@@ -19860,6 +19882,29 @@ var _TinyCloudNode = class _TinyCloudNode {
19860
19882
  const authHosts = this.auth?.hosts ?? [];
19861
19883
  return authHosts.length > 0 ? authHosts : [this.config.host];
19862
19884
  }
19885
+ /** The registry-resolved Node endpoint and the identity returned by that exact Node. */
19886
+ async activeNodeIdentity() {
19887
+ const host = this.hosts[0];
19888
+ if (host === void 0) throw new Error("TinyCloud node host has not been resolved");
19889
+ const parsed = new URL(host);
19890
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost")) || parsed.origin !== host) {
19891
+ throw new Error("TinyCloud node host is not a canonical origin");
19892
+ }
19893
+ const nodeDid = await this.fetchNodeId();
19894
+ if (!nodeDid.startsWith("did:key:z")) throw new Error("TinyCloud node identity is not a did:key");
19895
+ return Object.freeze({ origin: parsed.origin, nodeDid });
19896
+ }
19897
+ /** Publish the active owner Node as a session-signed registry record. */
19898
+ async publishActiveNodeLocation(registryUrl, fetchFn = globalThis.fetch.bind(globalThis)) {
19899
+ const node = await this.activeNodeIdentity();
19900
+ return publishLocationRecord({
19901
+ registryUrl,
19902
+ subject: this.credentialHolderDid,
19903
+ nodeOrigin: node.origin,
19904
+ signer: { type: "did:key", signBytes: (bytes) => this.signSessionBytes(bytes) },
19905
+ fetch: fetchFn
19906
+ });
19907
+ }
19863
19908
  /**
19864
19909
  * Get the primary identity DID for this user.
19865
19910
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -19901,16 +19946,26 @@ var _TinyCloudNode = class _TinyCloudNode {
19901
19946
  */
19902
19947
  getVerifiedSessionCapabilities() {
19903
19948
  const session = this.currentTinyCloudSession();
19904
- if (!session || !session.siwe) return [];
19905
- return parseRecapCapabilities(
19906
- (siwe) => this.parseRecapWithCaveats(siwe),
19907
- session.siwe
19908
- ).map((entry) => entry.service === "tinycloud.encryption" ? {
19909
- service: entry.service,
19910
- path: entry.path,
19911
- actions: [...entry.actions],
19912
- ...entry.caveats === void 0 ? {} : { caveats: entry.caveats }
19913
- } : entry);
19949
+ return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
19950
+ }
19951
+ /** Normalize signed capabilities without collapsing nested encryption into raw authority. */
19952
+ projectSignedRecapCapabilities(siwe) {
19953
+ const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
19954
+ const normalized = parseRecapCapabilities(() => [entry], siwe)[0];
19955
+ if (normalized.service !== "tinycloud.encryption") return normalized;
19956
+ if (!this.isEncryptionNetworkOperation("encryption", entry.path, entry.space)) {
19957
+ return { ...normalized, space: entry.space };
19958
+ }
19959
+ return {
19960
+ service: normalized.service,
19961
+ path: normalized.path,
19962
+ actions: normalized.actions,
19963
+ ...normalized.caveats === void 0 ? {} : { caveats: normalized.caveats }
19964
+ };
19965
+ });
19966
+ return projected.sort(
19967
+ (left, right) => (left.space ?? "").localeCompare(right.space ?? "") || left.service.localeCompare(right.service) || left.path.localeCompare(right.path)
19968
+ );
19914
19969
  }
19915
19970
  /**
19916
19971
  * Get the Ethereum address for this user.
@@ -20365,9 +20420,9 @@ var _TinyCloudNode = class _TinyCloudNode {
20365
20420
  * We must keep the full owner-scoped URI so a synthetic primary grant can
20366
20421
  * never cover an operation on a different owner's space.
20367
20422
  *
20368
- * Mirrors {@link operationsFromDelegation}'s op shape: encryption network
20369
- * entries (`urn:tinycloud:encryption:` paths) become `resource` ops; every
20370
- * other entry becomes a `spaceId` op carrying the raw recap `space` URI.
20423
+ * Only top-level encryption network entries (`space: "encryption"` or
20424
+ * absent) become `resource` ops. A network-shaped path nested under a
20425
+ * data space remains a `spaceId` op, not raw network authority.
20371
20426
  * One op per action.
20372
20427
  *
20373
20428
  * Returns `[]` for session-only / restored-without-siwe modes and for any
@@ -20388,7 +20443,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20388
20443
  operations = entries.flatMap((entry) => {
20389
20444
  const service = this.invocationServiceName(entry.service);
20390
20445
  return entry.actions.map((action) => ({
20391
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
20446
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
20392
20447
  service,
20393
20448
  path: entry.path,
20394
20449
  action,
@@ -20550,7 +20605,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20550
20605
  return;
20551
20606
  } catch (error) {
20552
20607
  const message = error instanceof Error ? error.message : String(error);
20553
- if (/Unauthorized Action|\b401\b/.test(message)) {
20608
+ if (/Unauthorized Action|\b(?:401|403)\b/.test(message)) {
20554
20609
  console.warn(
20555
20610
  "TinyCloud account registry sync stopped: authorization verdict is not retryable",
20556
20611
  error
@@ -20800,9 +20855,14 @@ var _TinyCloudNode = class _TinyCloudNode {
20800
20855
  */
20801
20856
  async restoreSession(sessionData) {
20802
20857
  await this.wasmBindings.ensureInitialized?.();
20803
- const restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
20858
+ let restoredJwk;
20859
+ try {
20860
+ restoredJwk = clonePersistedSessionJwk(sessionData.jwk);
20861
+ } catch {
20862
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
20863
+ }
20804
20864
  if (sessionData.chainId !== void 0 && (!Number.isSafeInteger(sessionData.chainId) || sessionData.chainId <= 0)) {
20805
- throw new Error("Persisted session chain ID must be a positive safe integer.");
20865
+ throw new InvalidRestoredSessionError("Persisted session chain ID must be a positive safe integer.");
20806
20866
  }
20807
20867
  const stagedManager = this.wasmBindings.createSessionManager();
20808
20868
  const stagedReplace = stagedManager.replaceSessionKey;
@@ -20814,32 +20874,31 @@ var _TinyCloudNode = class _TinyCloudNode {
20814
20874
  let stagedKeyId;
20815
20875
  let stagedJwk;
20816
20876
  let canonicalVerificationMethod;
20877
+ const keyIds = liveKeys.call(this.sessionManager);
20878
+ if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
20879
+ throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
20880
+ }
20881
+ for (const keyId of keyIds) {
20882
+ if (keyId === this.sessionKeyId) continue;
20883
+ const jwk = this.sessionManager.jwk(keyId);
20884
+ if (!jwk) throw new Error("missing live session key");
20885
+ stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
20886
+ }
20817
20887
  try {
20818
- const keyIds = liveKeys.call(this.sessionManager);
20819
- if (!Array.isArray(keyIds) || !keyIds.every((keyId) => typeof keyId === "string")) {
20820
- throw new UnsupportedSessionRestoreError("it cannot reliably enumerate every live session signer");
20821
- }
20822
- for (const keyId of keyIds) {
20823
- if (keyId === this.sessionKeyId) continue;
20824
- const jwk = this.sessionManager.jwk(keyId);
20825
- if (!jwk) throw new Error("missing live session key");
20826
- stagedReplace.call(stagedManager, clonePersistedSessionJwk(JSON.parse(jwk)), keyId);
20827
- }
20828
20888
  stagedKeyId = stagedReplace.call(stagedManager, restoredJwk, this.sessionKeyId);
20829
- const stagedJwkJson = stagedManager.jwk(stagedKeyId);
20830
- if (!stagedJwkJson) throw new Error("missing restored session key");
20831
- stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
20832
- canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
20833
- } catch (error) {
20834
- if (error instanceof UnsupportedSessionRestoreError) throw error;
20835
- throw new Error("Persisted session has an invalid private Ed25519 session key.");
20889
+ } catch {
20890
+ throw new InvalidRestoredSessionError("Persisted session has an invalid private Ed25519 session key.");
20836
20891
  }
20892
+ const stagedJwkJson = stagedManager.jwk(stagedKeyId);
20893
+ if (!stagedJwkJson) throw new Error("missing restored session key");
20894
+ stagedJwk = clonePersistedSessionJwk(JSON.parse(stagedJwkJson));
20895
+ canonicalVerificationMethod = stagedManager.getDID(stagedKeyId);
20837
20896
  const restoredVerificationMethod = canonicalRestoredVerificationMethod(
20838
20897
  canonicalVerificationMethod,
20839
20898
  sessionData.verificationMethod
20840
20899
  );
20841
20900
  if (!restoredVerificationMethod) {
20842
- throw new Error(
20901
+ throw new InvalidRestoredSessionError(
20843
20902
  "Persisted session verification method does not match its private Ed25519 session key."
20844
20903
  );
20845
20904
  }
@@ -20851,26 +20910,37 @@ var _TinyCloudNode = class _TinyCloudNode {
20851
20910
  ];
20852
20911
  const hasPersistedProof = proofValues.every((value) => value !== void 0);
20853
20912
  if (!hasPersistedProof && (proofValues.some((value) => value !== void 0) || sessionData.expiresAt !== void 0)) {
20854
- throw new Error("Persisted session authority metadata is incomplete.");
20913
+ throw new InvalidRestoredSessionError("Persisted session authority metadata is incomplete.");
20914
+ }
20915
+ let restoredAddress;
20916
+ try {
20917
+ restoredAddress = hasPersistedProof ? canonicalizeAddress2(sessionData.address) : void 0;
20918
+ } catch {
20919
+ throw new InvalidRestoredSessionError("Persisted session has an invalid wallet address.");
20855
20920
  }
20856
- const restoredAddress = hasPersistedProof ? canonicalizeAddress2(sessionData.address) : void 0;
20857
20921
  let stagedSessionExpiry = /* @__PURE__ */ new Date(0);
20858
20922
  let stagedRecap = [];
20859
20923
  if (hasPersistedProof) {
20860
20924
  if (typeof this.wasmBindings.validatePersistedSession !== "function") {
20861
20925
  throw new UnsupportedSessionRestoreError("it cannot verify persisted SIWE authority");
20862
20926
  }
20863
- const verified = this.wasmBindings.validatePersistedSession({
20864
- delegationHeader: sessionData.delegationHeader,
20865
- delegationCid: sessionData.delegationCid,
20866
- spaceId: sessionData.spaceId,
20867
- jwk: stagedJwk,
20868
- address: restoredAddress,
20869
- chainId: sessionData.chainId,
20870
- siwe: sessionData.siwe,
20871
- signature: sessionData.signature
20872
- });
20873
- const exactRecap = verified.verifiedRecap;
20927
+ const verified = (() => {
20928
+ try {
20929
+ return this.wasmBindings.validatePersistedSession({
20930
+ delegationHeader: sessionData.delegationHeader,
20931
+ delegationCid: sessionData.delegationCid,
20932
+ spaceId: sessionData.spaceId,
20933
+ jwk: stagedJwk,
20934
+ address: restoredAddress,
20935
+ chainId: sessionData.chainId,
20936
+ siwe: sessionData.siwe,
20937
+ signature: sessionData.signature
20938
+ });
20939
+ } catch (error) {
20940
+ throw new InvalidRestoredSessionError(error instanceof Error ? error.message : String(error));
20941
+ }
20942
+ })();
20943
+ const exactRecap = Array.isArray(verified.verifiedRecap) ? verifierRecapEntries(verified.verifiedRecap) : verified.verifiedRecap;
20874
20944
  if (!Array.isArray(exactRecap) || !exactRecap.every(
20875
20945
  (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(
20876
20946
  (caveat) => caveat !== null && typeof caveat === "object" && !Array.isArray(caveat)
@@ -20890,7 +20960,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20890
20960
  const signedExpiry = verified.expiresAt === void 0 ? void 0 : persistedExpiry(verified.expiresAt);
20891
20961
  const persistedPolicyExpiry = sessionData.expiresAt === void 0 ? void 0 : persistedExpiry(sessionData.expiresAt);
20892
20962
  if (signedExpiry && persistedPolicyExpiry && !sameInstant(signedExpiry, persistedPolicyExpiry)) {
20893
- throw new Error("Persisted session expiry does not match its signed SIWE authority.");
20963
+ throw new InvalidRestoredSessionError("Persisted session expiry does not match its signed SIWE authority.");
20894
20964
  }
20895
20965
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
20896
20966
  }
@@ -20987,7 +21057,7 @@ var _TinyCloudNode = class _TinyCloudNode {
20987
21057
  operations = recap.flatMap((entry) => {
20988
21058
  const service = this.invocationServiceName(entry.service);
20989
21059
  return entry.actions.map((action) => ({
20990
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId: entry.space },
21060
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId: entry.space },
20991
21061
  service,
20992
21062
  path: entry.path,
20993
21063
  action,
@@ -21083,7 +21153,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21083
21153
  const delegations = input.recap.map((entry) => ({
21084
21154
  cid: session.delegationCid,
21085
21155
  delegateDID: session.verificationMethod,
21086
- spaceId: entry.space,
21156
+ spaceId: this.spaceIdsEqual(entry.space, session.spaceId) ? session.spaceId : entry.space,
21087
21157
  path: entry.path,
21088
21158
  actions: [...entry.actions],
21089
21159
  caveats: cloneRecapCaveats(entry.caveats),
@@ -21773,16 +21843,11 @@ var _TinyCloudNode = class _TinyCloudNode {
21773
21843
  );
21774
21844
  graph.assertActive();
21775
21845
  if (!response.ok) {
21776
- let permissionHint;
21777
- if (response.status === 401 || response.status === 403) {
21778
- try {
21779
- const body = await response.json();
21780
- const record = typeof body === "object" && body !== null ? body : void 0;
21781
- const nested = typeof record?.error === "object" && record.error !== null ? record.error : void 0;
21782
- permissionHint = parsePermissionHint(record?.permissionHint) ?? parsePermissionHint(nested?.permissionHint);
21783
- } catch {
21784
- }
21785
- }
21846
+ const permissionHint = response.status === 401 || response.status === 403 ? parsePermissionHint({
21847
+ service: "tinycloud.encryption",
21848
+ path: networkId,
21849
+ actions: ["tinycloud.encryption/decrypt"]
21850
+ }) : void 0;
21786
21851
  throw new DecryptTransportResponseError(response.status, permissionHint);
21787
21852
  }
21788
21853
  return await response.json();
@@ -22216,16 +22281,16 @@ var _TinyCloudNode = class _TinyCloudNode {
22216
22281
  fetch: globalThis.fetch.bind(globalThis)
22217
22282
  });
22218
22283
  }
22219
- /** @deprecated Policy/v2 delivery transport is retired. Use authorizeShareDeliveryV3. */
22220
- async authorizeShareDelivery(input) {
22221
- void input;
22222
- throw new Error("Policy/v2 share delivery is retired; use authorizeShareDeliveryV3");
22223
- }
22224
22284
  /** Authorize one short-lived v3 delivery against the signed v3 envelope and registered roots. */
22225
22285
  async authorizeShareDeliveryV3(input) {
22226
22286
  const session = this.currentTinyCloudSession();
22227
22287
  const serviceSession = this._serviceContext?.session;
22228
22288
  if (!session || !serviceSession) throw new Error("Share delivery requires an authenticated session");
22289
+ const idempotencyBytes = new TextEncoder().encode(input.idempotencyKey);
22290
+ if (idempotencyBytes.byteLength === 0 || idempotencyBytes.byteLength > 512) {
22291
+ throw new Error("Share delivery idempotency key is invalid");
22292
+ }
22293
+ const jtiDigest = new Uint8Array(await crypto.subtle.digest("SHA-256", idempotencyBytes));
22229
22294
  const body = {
22230
22295
  envelope: input.envelope,
22231
22296
  sealedEnvelope: input.sealedEnvelope,
@@ -22234,7 +22299,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22234
22299
  recipientEmail: input.recipientEmail,
22235
22300
  shareUrl: input.shareUrl,
22236
22301
  documentName: input.documentName,
22237
- jti: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
22302
+ jti: base64UrlEncode(jtiDigest.slice(0, 16)),
22238
22303
  expiresAt: new Date(input.expiresAt).toISOString().replace(/\.\d{3}Z$/, "Z")
22239
22304
  };
22240
22305
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalizeEncryptionJson(body)))));
@@ -22249,9 +22314,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22249
22314
  const verified = validateShareDeliveryAuthorizationV3Bytes(new Uint8Array(await response.arrayBuffer()), {
22250
22315
  request,
22251
22316
  senderKeyDid: this.credentialHolderDid,
22252
- credentialsAudience: input.credentialsAudience
22317
+ deliveryAudience: input.deliveryAudience
22253
22318
  });
22254
- void input.nodeProof;
22255
22319
  const requestDigest = new Uint8Array(await crypto.subtle.digest(
22256
22320
  "SHA-256",
22257
22321
  new TextEncoder().encode(`${CREDENTIAL_INVITATION_REQUEST_DOMAIN}${canonicalizeEncryptionJson(verified.request)}`)
@@ -22718,7 +22782,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22718
22782
  continue;
22719
22783
  }
22720
22784
  let space;
22721
- if (service !== "tinycloud.encryption") {
22785
+ if (service !== "tinycloud.encryption" || operation.resource === void 0) {
22722
22786
  if (typeof operation.spaceId !== "string") continue;
22723
22787
  try {
22724
22788
  space = this.resolvePermissionSpace(operation.spaceId, session);
@@ -23263,11 +23327,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23263
23327
  );
23264
23328
  return { delegation: delegation2, prompted: true };
23265
23329
  }
23266
- const granted = parseRecapCapabilities(
23267
- (siwe) => this.parseRecapWithCaveats(siwe),
23268
- session.siwe
23269
- );
23270
- const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
23330
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
23331
+ const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
23271
23332
  if (!subset) {
23272
23333
  const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
23273
23334
  const runtimeGrant = this.findGrantForOperations(
@@ -23587,7 +23648,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23587
23648
  return abilities;
23588
23649
  }
23589
23650
  isEncryptionPermissionEntry(entry) {
23590
- return entry.service === ENCRYPTION_PERMISSION_SERVICE2 && entry.path.startsWith("urn:tinycloud:encryption:");
23651
+ return entry.service === ENCRYPTION_PERMISSION_SERVICE2 && this.isEncryptionNetworkOperation("encryption", entry.path, entry.space);
23591
23652
  }
23592
23653
  permissionsToRawAbilities(entries) {
23593
23654
  const rawAbilities = {};
@@ -23611,7 +23672,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23611
23672
  return entries.flatMap((entry) => {
23612
23673
  const service = this.shortServiceName(entry.service);
23613
23674
  return entry.actions.map((action) => ({
23614
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
23675
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
23615
23676
  service,
23616
23677
  path: entry.path,
23617
23678
  action,
@@ -23619,13 +23680,21 @@ var _TinyCloudNode = class _TinyCloudNode {
23619
23680
  }));
23620
23681
  });
23621
23682
  }
23683
+ /** Raw network authority cannot be covered by a nested encryption space. */
23684
+ signedCapabilitySubset(requested, granted) {
23685
+ const rawGranted = granted.filter((entry) => this.isEncryptionPermissionEntry(entry)).map((entry) => ({ ...entry, space: "encryption" }));
23686
+ const spaceGranted = granted.filter((entry) => !this.isEncryptionPermissionEntry(entry));
23687
+ const missing = requested.filter((entry) => {
23688
+ const raw = this.isEncryptionPermissionEntry(entry);
23689
+ const comparable = raw ? { ...entry, space: "encryption" } : entry;
23690
+ return !isCapabilitySubset([comparable], raw ? rawGranted : spaceGranted).subset;
23691
+ });
23692
+ return { subset: missing.length === 0, missing };
23693
+ }
23622
23694
  sessionCoversPermissionEntries(session, entries) {
23623
23695
  try {
23624
- const granted = parseRecapCapabilities(
23625
- (siwe) => this.parseRecapWithCaveats(siwe),
23626
- session.siwe
23627
- );
23628
- return isCapabilitySubset(entries, granted).subset;
23696
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
23697
+ return this.signedCapabilitySubset(entries, granted).subset;
23629
23698
  } catch {
23630
23699
  return false;
23631
23700
  }
@@ -23635,7 +23704,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23635
23704
  const spaceId = this.resolvePermissionSpace(entry.space, session);
23636
23705
  const service = this.shortServiceName(entry.service);
23637
23706
  return entry.actions.map((action) => ({
23638
- ...this.isEncryptionNetworkOperation(service, entry.path) ? { resource: entry.path } : { spaceId },
23707
+ ...this.isEncryptionNetworkOperation(service, entry.path, entry.space) ? { resource: entry.path } : { spaceId },
23639
23708
  service,
23640
23709
  path: entry.path,
23641
23710
  action,
@@ -23724,7 +23793,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23724
23793
  return resources.flatMap((resource) => {
23725
23794
  const service = this.invocationServiceName(resource.service);
23726
23795
  return resource.actions.map((action) => ({
23727
- ...this.isEncryptionNetworkOperation(service, resource.path) ? { resource: resource.path } : { spaceId: resource.space },
23796
+ ...this.isEncryptionNetworkOperation(service, resource.path, resource.space) ? { resource: resource.path } : { spaceId: resource.space },
23728
23797
  service,
23729
23798
  path: resource.path,
23730
23799
  action,
@@ -23785,7 +23854,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23785
23854
  };
23786
23855
  for (const resource of resources) {
23787
23856
  const service = this.invocationServiceName(resource.service);
23788
- if (this.isEncryptionNetworkOperation(service, resource.path)) {
23857
+ if (this.isEncryptionNetworkOperation(service, resource.path, resource.space)) {
23789
23858
  rawAbilities[_a = resource.path] ?? (rawAbilities[_a] = []);
23790
23859
  addActions(rawAbilities[resource.path], resource.actions);
23791
23860
  continue;
@@ -23854,21 +23923,15 @@ var _TinyCloudNode = class _TinyCloudNode {
23854
23923
  }
23855
23924
  return granted.spaceId !== void 0 && requested.spaceId !== void 0 && this.spaceIdsEqual(granted.spaceId, requested.spaceId) && this.pathContains(granted.path, requested.path);
23856
23925
  }
23857
- // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. The embedded
23858
- // EIP-155 address is case-insensitive, but the CLI canonicalizes it to
23859
- // lowercase when building a space URI while stored runtime delegations keep
23860
- // the EIP-55 checksummed form — so a byte-for-byte compare spuriously rejects
23861
- // an otherwise-valid grant. Lowercase ONLY the `eip155:<chain>:0x<addr>`
23862
- // segment and leave everything else (crucially the case-sensitive space NAME)
23863
- // byte-exact. Mirrors the CLI's `normalizeSpaceForCompare` (OPENKEY_SCOPE_MISMATCH fix).
23926
+ // Space IDs are `tinycloud:pkh:eip155:<chain>:<0xADDR>:<name>`. Compare
23927
+ // only the embedded EIP-155 address without case sensitivity; chain, name,
23928
+ // and all non-PKH space identifiers remain byte-exact.
23864
23929
  spaceIdsEqual(a, b) {
23865
23930
  return this.normalizeSpaceAddress(a) === this.normalizeSpaceAddress(b);
23866
23931
  }
23867
23932
  normalizeSpaceAddress(space) {
23868
- return space.replace(
23869
- /(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
23870
- (_match, prefix, addr) => prefix + addr.toLowerCase()
23871
- );
23933
+ const match = /^(tinycloud:pkh:eip155:\d+:)(0x[0-9a-fA-F]{40})(:.+)$/.exec(space);
23934
+ return match === null ? space : `${match[1]}${match[2].toLowerCase()}${match[3]}`;
23872
23935
  }
23873
23936
  actionContains(grantedAction, requestedAction) {
23874
23937
  if (grantedAction === requestedAction) {
@@ -23885,10 +23948,11 @@ var _TinyCloudNode = class _TinyCloudNode {
23885
23948
  }
23886
23949
  /** Prefer the v2 caveat-preserving parser while retaining old custom WASM. */
23887
23950
  parseRecapWithCaveats(siwe) {
23888
- return this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe) ?? this.wasmBindings.parseRecapFromSiwe(siwe);
23951
+ const verified = this.wasmBindings.parseVerifiedRecapFromSiwe?.(siwe);
23952
+ return verified == null ? this.wasmBindings.parseRecapFromSiwe(siwe) : verifierRecapEntries(verified);
23889
23953
  }
23890
- isEncryptionNetworkOperation(service, path) {
23891
- return service === "encryption" && path.startsWith("urn:tinycloud:encryption:");
23954
+ isEncryptionNetworkOperation(service, path, space) {
23955
+ return service === "encryption" && (space === void 0 || space === "encryption") && path.startsWith("urn:tinycloud:encryption:");
23892
23956
  }
23893
23957
  operationFromInvokeAnyEntry(entry) {
23894
23958
  const service = this.invocationServiceName(entry.service);
@@ -23901,7 +23965,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23901
23965
  ...entry.caveats === void 0 ? {} : { caveats: cloneRecapCaveats(entry.caveats) }
23902
23966
  };
23903
23967
  }
23904
- if (this.isEncryptionNetworkOperation(service, entry.path)) {
23968
+ if (entry.spaceId === void 0 && this.isEncryptionNetworkOperation(service, entry.path)) {
23905
23969
  return {
23906
23970
  resource: entry.path,
23907
23971
  service,
@@ -24186,8 +24250,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24186
24250
  const jwk = mySession.jwk;
24187
24251
  const { abilities, rawAbilities } = this.buildActivationAbilities(delegation);
24188
24252
  const now = /* @__PURE__ */ new Date();
24189
- const maxExpiry = new Date(now.getTime() + 60 * 60 * 1e3);
24190
- const expirationTime = delegation.expiry < maxExpiry ? delegation.expiry : maxExpiry;
24253
+ const expirationTime = delegation.expiry;
24191
24254
  const prepared = this.wasmBindings.prepareSession({
24192
24255
  abilities,
24193
24256
  address: this.wasmBindings.ensureEip55(mySession.address),
@@ -24291,8 +24354,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24291
24354
  );
24292
24355
  }
24293
24356
  const now = /* @__PURE__ */ new Date();
24294
- const expiryMs = params.expiryMs ?? 60 * 60 * 1e3;
24295
- const requestedExpiry = new Date(now.getTime() + expiryMs);
24357
+ const requestedExpiry = params.expiryMs === void 0 ? parentDelegation.expiry : new Date(now.getTime() + params.expiryMs);
24296
24358
  const actualExpiry = requestedExpiry > parentDelegation.expiry ? parentDelegation.expiry : requestedExpiry;
24297
24359
  const abilities = {};
24298
24360
  const rawAbilities = {};
@@ -25097,6 +25159,7 @@ export {
25097
25159
  FileSessionStorage,
25098
25160
  HooksService3 as HooksService,
25099
25161
  IdentityParseError,
25162
+ InvalidRestoredSessionError,
25100
25163
  KVService3 as KVService,
25101
25164
  ManifestValidationError,
25102
25165
  MemorySessionStorage,
@@ -25176,6 +25239,7 @@ export {
25176
25239
  expandActionShortNames,
25177
25240
  expandPermissionEntries2 as expandPermissionEntries,
25178
25241
  expandPermissionEntry,
25242
+ extractSiweExpiration,
25179
25243
  generateRandomReceiverKey,
25180
25244
  getTinyCloudDebugLogs,
25181
25245
  grantAuthRequest,