@tinycloud/node-sdk 3.8.0-beta.1 → 3.8.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/{TinyCloudNode-Ckqu_fqH.d.ts → TinyCloudNode-BVDUdEcf.d.cts} +53 -5
  2. package/dist/{TinyCloudNode-DDfx8Fx5.d.cts → TinyCloudNode-jMK_2Uj8.d.ts} +53 -5
  3. package/dist/{authority-BwM_s3Ya.d.cts → authority-DJ-PE4Xo.d.cts} +20 -4
  4. package/dist/{authority-DcyJpajL.d.ts → authority-i22vHbbp.d.ts} +20 -4
  5. package/dist/core.cjs +300 -32
  6. package/dist/core.cjs.map +1 -1
  7. package/dist/core.d.cts +5 -4
  8. package/dist/core.d.ts +5 -4
  9. package/dist/core.js +292 -27
  10. package/dist/core.js.map +1 -1
  11. package/dist/credentials-core.d.cts +1 -1
  12. package/dist/credentials-core.d.ts +1 -1
  13. package/dist/credentials.d.cts +1 -1
  14. package/dist/credentials.d.ts +1 -1
  15. package/dist/{delegation-0LKZkmP2.d.cts → delegation-Ckc3UETl.d.cts} +1 -1
  16. package/dist/{delegation-0LKZkmP2.d.ts → delegation-Ckc3UETl.d.ts} +1 -1
  17. package/dist/encryption-keys.d.cts +2 -2
  18. package/dist/encryption-keys.d.ts +2 -2
  19. package/dist/index.cjs +304 -44
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +4 -3
  22. package/dist/index.d.ts +4 -3
  23. package/dist/index.js +291 -31
  24. package/dist/index.js.map +1 -1
  25. package/dist/replication/authority.cjs +39 -0
  26. package/dist/replication/authority.cjs.map +1 -1
  27. package/dist/replication/authority.d.cts +4 -3
  28. package/dist/replication/authority.d.ts +4 -3
  29. package/dist/replication/authority.js +38 -0
  30. package/dist/replication/authority.js.map +1 -1
  31. package/dist/replication/runtime.cjs +1 -0
  32. package/dist/replication/runtime.cjs.map +1 -1
  33. package/dist/replication/runtime.d.cts +1 -1
  34. package/dist/replication/runtime.d.ts +1 -1
  35. package/dist/replication/runtime.js +1 -0
  36. package/dist/replication/runtime.js.map +1 -1
  37. package/dist/{runtime-v62FzQHz.d.ts → runtime-CO4VXnPq.d.ts} +2 -1
  38. package/dist/{runtime-D1o9Eybt.d.cts → runtime-mRhSu-vJ.d.cts} +2 -1
  39. package/package.json +4 -4
package/dist/index.cjs CHANGED
@@ -17089,6 +17089,9 @@ function assertValidReplicationConfig(replication) {
17089
17089
  if (replication?.syncScope !== void 0 && replication.syncScope !== "prefixes") {
17090
17090
  throw new TypeError('replication.syncScope must be "prefixes"');
17091
17091
  }
17092
+ if (replication?.contentFetch !== void 0 && replication.contentFetch !== "eager" && replication.contentFetch !== "lazy") {
17093
+ throw new TypeError('replication.contentFetch must be "eager" or "lazy"');
17094
+ }
17092
17095
  if (replication === void 0 || !replication.enabled) return;
17093
17096
  if (replication.storage === void 0 || replication.storage === null) {
17094
17097
  throw new TypeError("replication.enabled requires replication.storage");
@@ -17143,6 +17146,7 @@ function resolvedOptions(options) {
17143
17146
  maxStalenessMs: options.maxStalenessMs ?? 12e4,
17144
17147
  staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5e3,
17145
17148
  allowSecrets: options.allowSecrets === true,
17149
+ contentFetch: options.contentFetch ?? "eager",
17146
17150
  verify: options.verify === true
17147
17151
  };
17148
17152
  }
@@ -17414,6 +17418,7 @@ __export(authority_exports, {
17414
17418
  assertValidReplicationConfig: () => assertValidReplicationConfig,
17415
17419
  augmentSignInEntriesWithReplication: () => augmentSignInEntriesWithReplication,
17416
17420
  createReplicationAuthority: () => createReplicationAuthority,
17421
+ eraseReplicaLocalCopy: () => eraseReplicaLocalCopy,
17417
17422
  establishReplicaSessionStamp: () => establishReplicaSessionStamp,
17418
17423
  hasUnrestrictedGetCoverage: () => hasUnrestrictedGetCoverage,
17419
17424
  mintNodeReplicationGrant: () => mintNodeReplicationGrant,
@@ -17444,6 +17449,38 @@ function reportReplicaDenial(result, reason, onEvent, context) {
17444
17449
  } catch {
17445
17450
  }
17446
17451
  }
17452
+ async function eraseReplicaLocalCopy(lifecycle, trigger, denyStatus, handlers) {
17453
+ if (lifecycle?.kind !== "indexeddb" || lifecycle.eraseLocalCopy === void 0) return void 0;
17454
+ let outcome = { status: "failed", code: "STORAGE_ERROR", marker: false };
17455
+ for (let attempt = 0; attempt <= ERASE_RETRY_DELAYS_MS.length; attempt++) {
17456
+ if (attempt > 0) await new Promise((resolve2) => setTimeout(resolve2, ERASE_RETRY_DELAYS_MS[attempt - 1]));
17457
+ try {
17458
+ outcome = await lifecycle.eraseLocalCopy({ signal: AbortSignal.timeout(ERASE_ATTEMPT_MS) });
17459
+ } catch {
17460
+ outcome = { status: "failed", code: "STORAGE_ERROR", marker: false };
17461
+ }
17462
+ if (outcome.status !== "failed") break;
17463
+ }
17464
+ const keysKept = denyStatus === "marker" || denyStatus === "unguarded";
17465
+ const purge = outcome.status === "erased" ? { ok: true } : { ok: false, retryNow: outcome.status === "failed" };
17466
+ if (outcome.status === "erased" || outcome.status === "refused" && !keysKept) return { outcome, purge };
17467
+ const logoutPending = outcome.status === "failed" && outcome.marker;
17468
+ const event = {
17469
+ type: "replica.purge.failed",
17470
+ trigger,
17471
+ reason: logoutPending ? "logout_pending" : "gate_unavailable",
17472
+ code: outcome.code,
17473
+ retryNow: outcome.status === "failed"
17474
+ };
17475
+ for (const handler of handlers) {
17476
+ try {
17477
+ handler(event);
17478
+ } catch (error) {
17479
+ console.error('Error in "replica.purge.failed" handler:', error);
17480
+ }
17481
+ }
17482
+ return logoutPending ? { outcome, purge, logoutPending } : { outcome, purge };
17483
+ }
17447
17484
  function verifiedOwnerConsentAnchor(session, wasmBindings, exactVerifiedRecap2) {
17448
17485
  if (typeof session.siwe !== "string" || typeof session.signature !== "string" || typeof session.address !== "string" || typeof session.chainId !== "number") return null;
17449
17486
  try {
@@ -17702,7 +17739,7 @@ function createReplicationAuthority(host) {
17702
17739
  };
17703
17740
  return authority;
17704
17741
  }
17705
- var import_siwe3, import_sdk_core12, import_replica, parseUcanGrant, KV_GET2, KV_SYNC2, KV_SERVICE2, AUTHORITY_EXPIRY_MARGIN_MS;
17742
+ var import_siwe3, import_sdk_core12, import_replica, parseUcanGrant, ERASE_RETRY_DELAYS_MS, ERASE_ATTEMPT_MS, KV_GET2, KV_SYNC2, KV_SERVICE2, AUTHORITY_EXPIRY_MARGIN_MS;
17706
17743
  var init_authority = __esm({
17707
17744
  "src/replication/authority.ts"() {
17708
17745
  "use strict";
@@ -17712,6 +17749,8 @@ var init_authority = __esm({
17712
17749
  init_config();
17713
17750
  init_authority_sign_in();
17714
17751
  parseUcanGrant = import_replica.parseUcanGrant;
17752
+ ERASE_RETRY_DELAYS_MS = [150, 450];
17753
+ ERASE_ATTEMPT_MS = 3e3;
17715
17754
  KV_GET2 = import_sdk_core12.KV.GET;
17716
17755
  KV_SYNC2 = import_sdk_core12.KV.SYNC;
17717
17756
  KV_SERVICE2 = "tinycloud.kv";
@@ -18789,13 +18828,42 @@ function getNodeReplicationLoaders() {
18789
18828
 
18790
18829
  // src/TinyCloudNode.ts
18791
18830
  var import_sdk_core10 = require("@tinycloud/sdk-core");
18792
- var import_sdk_services3 = require("@tinycloud/sdk-services");
18831
+ var import_sdk_services4 = require("@tinycloud/sdk-services");
18793
18832
  var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
18833
+ var import_resource2 = require("@tinycloud/sdk-services/encryption/resource");
18794
18834
 
18795
18835
  // src/authorization/NodeUserAuthorization.ts
18796
18836
  var import_node_crypto = require("crypto");
18797
18837
  var import_siwe = require("siwe");
18798
18838
  var import_sdk_core5 = require("@tinycloud/sdk-core");
18839
+ var import_resource = require("@tinycloud/sdk-services/encryption/resource");
18840
+ var import_sdk_services3 = require("@tinycloud/sdk-services");
18841
+
18842
+ // src/authorization/appKeyHostBinding.ts
18843
+ var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
18844
+ var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
18845
+ var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
18846
+ function appKeyHostBinding(domain, appId) {
18847
+ if (!APP_ID.test(appId)) return false;
18848
+ if (domain.endsWith(":")) return false;
18849
+ let url;
18850
+ try {
18851
+ url = new URL(`https://${domain}`);
18852
+ } catch {
18853
+ return false;
18854
+ }
18855
+ if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
18856
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
18857
+ const host = url.hostname;
18858
+ if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
18859
+ const labels = host.split(".");
18860
+ if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
18861
+ if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
18862
+ const reversed = labels.reverse().join(".");
18863
+ return appId === reversed || appId.startsWith(`${reversed}.`);
18864
+ }
18865
+
18866
+ // src/authorization/NodeUserAuthorization.ts
18799
18867
  init_authority_sign_in();
18800
18868
 
18801
18869
  // src/authorization/caveatRefusal.ts
@@ -18951,6 +19019,7 @@ function cloneAbilitiesMap(actions) {
18951
19019
  var NodeUserAuthorization = class {
18952
19020
  constructor(config) {
18953
19021
  this.activeSessionKeyId = "default";
19022
+ this.preparedSessionCounter = 0;
18954
19023
  /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
18955
19024
  this.sessionGeneration = 0;
18956
19025
  /**
@@ -19307,6 +19376,23 @@ var NodeUserAuthorization = class {
19307
19376
  );
19308
19377
  return this._capabilityRequest;
19309
19378
  }
19379
+ async refreshNamedKeyFeatureForSignIn(address, chainId) {
19380
+ const request = this.getCapabilityRequest();
19381
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19382
+ const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
19383
+ if (hasNamedKeys) {
19384
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
19385
+ await this.refreshNodeInfo();
19386
+ }
19387
+ }
19388
+ /** Manifest key paths that this sign-in cannot grant. */
19389
+ get unavailableKeyPaths() {
19390
+ const request = this.getCapabilityRequest();
19391
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19392
+ return (request?.manifests ?? []).flatMap(
19393
+ (manifest) => this._nodeFeatures.includes("encryption-named-keys-v1") && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id) ? [] : (manifest.keys ?? []).map((entry) => "path" in entry ? entry.path : entry.prefix)
19394
+ );
19395
+ }
19310
19396
  resolveSpaceName(space, address, chainId) {
19311
19397
  if (space.startsWith("tinycloud:")) {
19312
19398
  return space;
@@ -19498,15 +19584,31 @@ var NodeUserAuthorization = class {
19498
19584
  }
19499
19585
  const rawAbilities = {};
19500
19586
  const currentDid = (0, import_sdk_core5.pkhDid)(address, chainId);
19587
+ if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
19588
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19589
+ for (const manifest of request.manifests) {
19590
+ if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
19591
+ for (const declaration of manifest.keys ?? []) {
19592
+ const path = "path" in declaration ? declaration.path : declaration.prefix;
19593
+ const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
19594
+ for (const ability of declaration.abilities) {
19595
+ addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
19596
+ }
19597
+ }
19598
+ }
19599
+ }
19501
19600
  const spaceResources = request.resources.filter((entry) => {
19502
19601
  if (entry.service !== import_sdk_core5.ENCRYPTION_PERMISSION_SERVICE) {
19503
19602
  return true;
19504
19603
  }
19604
+ const parsed = entry.path.includes("/") ? (0, import_resource.parseEncryptionResource)(entry.path) : { ...(0, import_sdk_services3.parseNetworkId)(entry.path), kind: "network" };
19605
+ if (parsed.kind !== "network") {
19606
+ throw new import_sdk_core5.ManifestValidationError("named keys go in manifest.keys");
19607
+ }
19505
19608
  for (const action of entry.actions) {
19506
19609
  addRawAbility(rawAbilities, entry.path, action);
19507
19610
  }
19508
- if (entry.actions.includes(DECRYPT_ACTION)) {
19509
- const parsed = (0, import_sdk_core5.parseNetworkId)(entry.path);
19611
+ if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
19510
19612
  if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
19511
19613
  addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
19512
19614
  }
@@ -19902,6 +20004,8 @@ var NodeUserAuthorization = class {
19902
20004
  this._chainId = chainId;
19903
20005
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
19904
20006
  this.assertSignInCurrent(generation);
20007
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20008
+ this.assertSignInCurrent(generation);
19905
20009
  const keyId = `session-${Date.now()}`;
19906
20010
  this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
19907
20011
  this.activeSessionKeyId = keyId;
@@ -20048,9 +20152,9 @@ var NodeUserAuthorization = class {
20048
20152
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20049
20153
  const address = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
20050
20154
  const chainId = await this.signer.getChainId();
20051
- const keyId = `session-${Date.now()}`;
20052
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
20053
- this.activeSessionKeyId = keyId;
20155
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20156
+ const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
20157
+ this.sessionManager.createSessionKey(keyId);
20054
20158
  const jwkString = this.sessionManager.jwk(keyId);
20055
20159
  if (!jwkString) {
20056
20160
  throw new Error("Failed to create session key");
@@ -20131,6 +20235,7 @@ var NodeUserAuthorization = class {
20131
20235
  await this.sessionStorage.save(address, persistedData);
20132
20236
  this._session = clientSession;
20133
20237
  this._tinyCloudSession = tinyCloudSession;
20238
+ this.activeSessionKeyId = keyId;
20134
20239
  this._address = address;
20135
20240
  this._chainId = chainId;
20136
20241
  await this.refreshNodeInfo();
@@ -20177,6 +20282,11 @@ var NodeUserAuthorization = class {
20177
20282
  * @param options Optional overrides for the OpenKey request.
20178
20283
  */
20179
20284
  async signInWithOpenKey(authorizeFn, options) {
20285
+ const pending = await this.prepareOpenKeySignIn(options);
20286
+ return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
20287
+ }
20288
+ /** Finish address, host and SIWE preparation before an approval click. */
20289
+ async prepareOpenKeySignIn(options) {
20180
20290
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20181
20291
  if (options?.host !== void 0) {
20182
20292
  const overrideHost = options.host.trim();
@@ -20188,7 +20298,7 @@ var NodeUserAuthorization = class {
20188
20298
  const signingAddress = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
20189
20299
  const signingChainId = await this.signer.getChainId();
20190
20300
  await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
20191
- const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
20301
+ const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
20192
20302
  const presentation = this.buildPresentationEnvelope(options?.reason);
20193
20303
  const hostHint = this.primaryTinyCloudHost;
20194
20304
  const originalRequest = {
@@ -20200,12 +20310,32 @@ var NodeUserAuthorization = class {
20200
20310
  keyId: options?.openkeyKeyId,
20201
20311
  presentation
20202
20312
  };
20313
+ return {
20314
+ request: originalRequest,
20315
+ prepared,
20316
+ keyId,
20317
+ jwk,
20318
+ address,
20319
+ chainId,
20320
+ generation: this.sessionGeneration,
20321
+ createdAt: Date.now()
20322
+ };
20323
+ }
20324
+ /** Complete a prepared approval. The caller starts authorization before awaiting this method. */
20325
+ async completeOpenKeySignIn(pending, authorization, options) {
20326
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
20327
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
20328
+ }
20329
+ if (pending.generation !== this.sessionGeneration) {
20330
+ throw new Error("Prepared OpenKey sign-in expired after sign-out");
20331
+ }
20332
+ const { request: originalRequest, prepared, keyId, jwk } = pending;
20203
20333
  let result;
20204
20334
  let signed2;
20205
20335
  let signature2;
20206
20336
  for (let attempt = 0; ; ) {
20207
20337
  try {
20208
- result ?? (result = await authorizeFn(originalRequest));
20338
+ result ?? (result = await authorization);
20209
20339
  signed2 = await this.verifyOpenKeyResult(result, prepared);
20210
20340
  signature2 = result.signature;
20211
20341
  break;
@@ -21101,6 +21231,7 @@ var ROOT_DELEGATION_ACTIONS = [
21101
21231
  import_sdk_core10.DUCKDB.ALL
21102
21232
  ];
21103
21233
  var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core10.EXPIRY.SESSION_MS;
21234
+ var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
21104
21235
  function unifiedRootAttenuation(capabilities) {
21105
21236
  const attenuation = {};
21106
21237
  for (const capability of capabilities) {
@@ -21565,6 +21696,7 @@ function replicaRestoreRefusal(error) {
21565
21696
  case "purge_pending":
21566
21697
  case "logout_pending":
21567
21698
  case "gate_unavailable":
21699
+ case "wallet_provider_required":
21568
21700
  return error.gate;
21569
21701
  }
21570
21702
  }
@@ -21597,8 +21729,10 @@ var _TinyCloudNode = class _TinyCloudNode {
21597
21729
  constructor(config = {}) {
21598
21730
  this.signer = null;
21599
21731
  this.auth = null;
21732
+ this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
21600
21733
  this.tc = null;
21601
21734
  this._chainId = 1;
21735
+ this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
21602
21736
  /** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
21603
21737
  this.establishReplicaStamp = async (session) => {
21604
21738
  const replication = this.config.replication;
@@ -21630,6 +21764,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21630
21764
  return void 0;
21631
21765
  }
21632
21766
  };
21767
+ this._keysUnavailable = [];
21633
21768
  this._baseVaults = /* @__PURE__ */ new Map();
21634
21769
  this._baseSecrets = /* @__PURE__ */ new Map();
21635
21770
  this._secrets = /* @__PURE__ */ new Map();
@@ -21805,6 +21940,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21805
21940
  return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
21806
21941
  },
21807
21942
  clearPending: async () => (await runtime).control.clearPending(),
21943
+ purgeLocalCopy: async () => (await this.eraseReplicaLocalCopy("purge_local_copy"))?.purge ?? { ok: false, retryNow: false },
21808
21944
  close: async () => (await runtime).control.close()
21809
21945
  };
21810
21946
  }
@@ -21859,18 +21995,14 @@ var _TinyCloudNode = class _TinyCloudNode {
21859
21995
  get freshEncryptionNetworkCreated() {
21860
21996
  return this._freshEncryptionNetworkCreated;
21861
21997
  }
21862
- /**
21863
- * Subscribe to `storage.full`. It fires on the first write any service had
21864
- * rejected because the owner's TinyCloud storage is full
21865
- * (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`), so an app can show
21866
- * one read-only banner. Reads keep working. For the signed-in account's own
21867
- * storage it fires once per episode: the episode ends only when a write to
21868
- * that storage succeeds, or on sign-out. A `storage.status()` read does not
21869
- * end it. A rejection on another owner's delegated space fires once per
21870
- * owner, until that owner's `storage.status()` reports space again. Returns
21871
- * the unsubscribe function.
21872
- */
21873
21998
  on(event, handler) {
21999
+ if (event === "replica.purge.failed") {
22000
+ const listener = handler;
22001
+ this.replicaPurgeFailedHandlers.add(listener);
22002
+ return () => {
22003
+ this.replicaPurgeFailedHandlers.delete(listener);
22004
+ };
22005
+ }
21874
22006
  if (event !== "storage.full") {
21875
22007
  throw new Error(`Unknown TinyCloud event: ${String(event)}`);
21876
22008
  }
@@ -22351,6 +22483,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22351
22483
  const leavingAuth = this.auth;
22352
22484
  const leavingContext = this._serviceContext;
22353
22485
  let denied;
22486
+ let erased;
22354
22487
  try {
22355
22488
  this.teardownSession();
22356
22489
  } finally {
@@ -22361,6 +22494,7 @@ var _TinyCloudNode = class _TinyCloudNode {
22361
22494
  try {
22362
22495
  await this.accountRegistryTail;
22363
22496
  await this.quiesceReplicaRuntime();
22497
+ if (denied) erased = await this.eraseReplicaLocalCopy("sign_out", denied.status);
22364
22498
  } finally {
22365
22499
  if (leavingAddress) {
22366
22500
  await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
@@ -22368,8 +22502,37 @@ var _TinyCloudNode = class _TinyCloudNode {
22368
22502
  }
22369
22503
  }
22370
22504
  }
22371
- if (denied?.status === "unguarded") {
22372
- throw new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`);
22505
+ if (denied?.status === "unguarded" && erased?.outcome.status !== "erased") {
22506
+ throw Object.assign(
22507
+ new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`),
22508
+ {
22509
+ code: "REPLICA_PURGE_FAILED",
22510
+ reason: "gate_unavailable",
22511
+ retryNow: erased?.outcome.status === "failed"
22512
+ }
22513
+ );
22514
+ }
22515
+ }
22516
+ /**
22517
+ * Erases the browser local copy after a deny (retry, event and next-load marker live in the lazy
22518
+ * replication authority). Never rejects; `undefined` when replication is off or the storage keeps
22519
+ * no browser database.
22520
+ */
22521
+ async eraseReplicaLocalCopy(trigger, denyStatus) {
22522
+ const replication = this.config.replication;
22523
+ if (!replication?.enabled) return void 0;
22524
+ try {
22525
+ const { eraseReplicaLocalCopy: eraseReplicaLocalCopy2 } = await getNodeReplicationLoaders().authority();
22526
+ const erased = await eraseReplicaLocalCopy2(
22527
+ replication.storage.lifecycle,
22528
+ trigger,
22529
+ denyStatus,
22530
+ this.replicaPurgeFailedHandlers
22531
+ );
22532
+ if (erased?.logoutPending) this.replicaRestoreGate = "logout_pending";
22533
+ return erased;
22534
+ } catch {
22535
+ return void 0;
22373
22536
  }
22374
22537
  }
22375
22538
  /**
@@ -22429,6 +22592,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22429
22592
  this._hooks = void 0;
22430
22593
  this._vault = void 0;
22431
22594
  this._encryption = void 0;
22595
+ this._namedKeyClient = void 0;
22596
+ this._keysUnavailable = [];
22432
22597
  this._baseVaults.clear();
22433
22598
  this._baseSecrets.clear();
22434
22599
  this._secrets.clear();
@@ -22505,6 +22670,37 @@ var _TinyCloudNode = class _TinyCloudNode {
22505
22670
  }
22506
22671
  await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
22507
22672
  }
22673
+ /** Prepare the OpenKey request before the approval click. */
22674
+ async prepareOpenKeySignIn() {
22675
+ if (!this.signer || !this.tc || !this.auth) {
22676
+ throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
22677
+ }
22678
+ await this.signOutInFlight;
22679
+ await this.accountRegistryTail;
22680
+ await this.wasmBindings.ensureInitialized?.();
22681
+ return this.auth.prepareOpenKeySignIn();
22682
+ }
22683
+ /** Start OpenKey authorization synchronously, then complete normal session setup. */
22684
+ signInWithPreparedOpenKey(pending, authorizeFn) {
22685
+ if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
22686
+ throw new Error("Prepared OpenKey sign-in is unavailable");
22687
+ }
22688
+ if (this.consumedOpenKeyPendings.has(pending)) {
22689
+ throw new Error("Prepared OpenKey sign-in was already used");
22690
+ }
22691
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
22692
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
22693
+ }
22694
+ this.consumedOpenKeyPendings.add(pending);
22695
+ const authorization = authorizeFn(pending.request);
22696
+ void authorization.catch(() => void 0);
22697
+ return this.signInWith(() => {
22698
+ if (this._address !== pending.address || this._chainId !== pending.chainId) {
22699
+ throw new Error("OpenKey account changed after sign-in preparation");
22700
+ }
22701
+ return this.auth.completeOpenKeySignIn(pending, authorization);
22702
+ });
22703
+ }
22508
22704
  async signInWith(performSignIn) {
22509
22705
  refuseCaveated(this.auth?.capabilityRequest?.manifests);
22510
22706
  const epoch = this.sessionEpoch;
@@ -22533,6 +22729,10 @@ var _TinyCloudNode = class _TinyCloudNode {
22533
22729
  const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? (0, import_sdk_core10.pkhDid)(this.address, this._chainId) : void 0);
22534
22730
  if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
22535
22731
  const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
22732
+ if (denial) {
22733
+ await this.quiesceReplicaRuntime();
22734
+ await this.eraseReplicaLocalCopy("account_switch", denial.status);
22735
+ }
22536
22736
  this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
22537
22737
  }
22538
22738
  if (address !== this._address || chainId !== this._chainId) {
@@ -22547,6 +22747,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22547
22747
  this._hooks = void 0;
22548
22748
  this._vault = void 0;
22549
22749
  this._encryption = void 0;
22750
+ this._namedKeyClient = void 0;
22751
+ this._keysUnavailable = [];
22550
22752
  this._baseVaults.clear();
22551
22753
  this._baseSecrets.clear();
22552
22754
  this._secrets.clear();
@@ -22599,6 +22801,26 @@ var _TinyCloudNode = class _TinyCloudNode {
22599
22801
  this.assertSessionEpoch(epoch, "Sign-in");
22600
22802
  await this.ensureRequestedEncryptionNetworks();
22601
22803
  this.assertSessionEpoch(epoch, "Sign-in");
22804
+ this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
22805
+ if (this._keysUnavailable.length > 0) {
22806
+ this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
22807
+ }
22808
+ if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
22809
+ for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
22810
+ for (const declaration of manifest.keys ?? []) {
22811
+ if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
22812
+ try {
22813
+ await this.keys.ensureKey(declaration.path);
22814
+ } catch (error) {
22815
+ this._keysUnavailable.push(declaration.path);
22816
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
22817
+ this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
22818
+ }
22819
+ this.assertSessionEpoch(epoch, "Sign-in");
22820
+ }
22821
+ }
22822
+ }
22823
+ }
22602
22824
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
22603
22825
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(import_sdk_core10.SECRETS_SPACE));
22604
22826
  }
@@ -23175,7 +23397,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23175
23397
  if (!this.currentSessionCoversAccountRegistry()) {
23176
23398
  if (!_TinyCloudNode.accountRegistrySkipLogged) {
23177
23399
  _TinyCloudNode.accountRegistrySkipLogged = true;
23178
- import_sdk_services3.tinyCloudDebugLogger.log("registry.skip");
23400
+ import_sdk_services4.tinyCloudDebugLogger.log("registry.skip");
23179
23401
  }
23180
23402
  return;
23181
23403
  }
@@ -23386,7 +23608,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23386
23608
  const networkIds = /* @__PURE__ */ new Set();
23387
23609
  for (const resource of request.resources) {
23388
23610
  if (resource.service === import_sdk_core10.ENCRYPTION_PERMISSION_SERVICE && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
23389
- networkIds.add(resource.path);
23611
+ const parsed = resource.path.includes("/") ? (0, import_resource2.parseEncryptionResource)(resource.path) : { ...(0, import_sdk_core10.parseNetworkId)(resource.path), kind: "network" };
23612
+ if (parsed.kind === "network") networkIds.add(resource.path);
23390
23613
  }
23391
23614
  }
23392
23615
  return [...networkIds];
@@ -23936,6 +24159,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23936
24159
  this._hooks = stagedGraph.hooks;
23937
24160
  this._vault = stagedGraph.vault;
23938
24161
  this._encryption = stagedGraph.encryption;
24162
+ this._namedKeyClient = void 0;
24163
+ this._keysUnavailable = [];
23939
24164
  this._capabilityRegistry = stagedGraph.capabilityRegistry;
23940
24165
  this._keyProvider = stagedGraph.keyProvider;
23941
24166
  this._sharingService = stagedGraph.sharingService;
@@ -24845,7 +25070,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24845
25070
  );
24846
25071
  graph.assertActive();
24847
25072
  if (!response.ok) {
24848
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services3.parsePermissionHint)({
25073
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services4.parsePermissionHint)({
24849
25074
  service: "tinycloud.encryption",
24850
25075
  path: networkId,
24851
25076
  actions: ["tinycloud.encryption/decrypt"]
@@ -25289,7 +25514,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25289
25514
  * Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
25290
25515
  * `POST /revoke`, and return its Authorization header value. The node
25291
25516
  * accepts it when the owner delegated `cid` or owns a space its abilities
25292
- * target, whichever key issued it. Valid for 60 seconds. Requires the
25517
+ * target, whichever key issued it. Valid for 10 minutes. Requires the
25293
25518
  * owner wallet signer.
25294
25519
  */
25295
25520
  async signOwnerRevocation(cid) {
@@ -25305,7 +25530,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25305
25530
  chainId: session.chainId,
25306
25531
  nonce: (0, import_siwe2.generateNonce)(),
25307
25532
  issuedAt: issuedAt.toISOString(),
25308
- expirationTime: new Date(issuedAt.getTime() + 6e4).toISOString()
25533
+ expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
25309
25534
  }).prepareMessage();
25310
25535
  const signature2 = await this.signer.signMessage(siwe);
25311
25536
  return this.wasmBindings.siweToDelegationHeaders({ siwe, signature: signature2 }).Authorization;
@@ -25532,6 +25757,40 @@ var _TinyCloudNode = class _TinyCloudNode {
25532
25757
  get encryption() {
25533
25758
  return this.getEncryptionService();
25534
25759
  }
25760
+ /** Named keys bound to the current session. */
25761
+ get keys() {
25762
+ return new Proxy({}, {
25763
+ get: (_target, property) => {
25764
+ if (property === "refreshNodeInfo") {
25765
+ return () => {
25766
+ void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
25767
+ };
25768
+ }
25769
+ if (typeof property !== "string" || ![
25770
+ "getPublicKey",
25771
+ "describeKey",
25772
+ "createKey",
25773
+ "blockKey",
25774
+ "unblockKey",
25775
+ "ensureKey",
25776
+ "encrypt",
25777
+ "decrypt",
25778
+ "decryptMany"
25779
+ ].includes(property)) return void 0;
25780
+ return (...args) => {
25781
+ this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.cjs").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
25782
+ return this._namedKeyClient.then((client) => {
25783
+ const method = client[property];
25784
+ return method.apply(client, args);
25785
+ });
25786
+ };
25787
+ }
25788
+ });
25789
+ }
25790
+ /** Manifest key paths unavailable during the latest `signIn()` attempt. */
25791
+ get keysUnavailable() {
25792
+ return this._keysUnavailable;
25793
+ }
25535
25794
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
25536
25795
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
25537
25796
  return this.fetchEncryptionNetworkAt(
@@ -25659,7 +25918,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25659
25918
  );
25660
25919
  if (result.status !== "ok") {
25661
25920
  if (result.status === "permission_required") {
25662
- const hint = (0, import_sdk_services3.parsePermissionHint)(result.hint);
25921
+ const hint = (0, import_sdk_services4.parsePermissionHint)(result.hint);
25663
25922
  if (hint === void 0) return { status: "read_failed" };
25664
25923
  return {
25665
25924
  status: "permission_required",
@@ -27940,7 +28199,7 @@ var FileSessionStorage = class {
27940
28199
  };
27941
28200
 
27942
28201
  // src/authorization/openKeyBridge.ts
27943
- function wireOpenKeyAuthorize(openkey) {
28202
+ function wireOpenKeyAuthorize(openkey, opts) {
27944
28203
  return async (input) => {
27945
28204
  const result = await openkey.authorizeTinyCloud({
27946
28205
  protocolVersion: 1,
@@ -27951,7 +28210,7 @@ function wireOpenKeyAuthorize(openkey) {
27951
28210
  // Forward the presentation envelope verbatim — the OpenKey SDK
27952
28211
  // validates and size-bounds it before use.
27953
28212
  presentation: input.presentation
27954
- });
28213
+ }, opts);
27955
28214
  if (result.protocolVersion !== 1) {
27956
28215
  throw new Error(
27957
28216
  `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
@@ -28004,7 +28263,7 @@ var import_node_fs = require("fs");
28004
28263
  var import_promises = require("fs/promises");
28005
28264
  var import_node_path = require("path");
28006
28265
  var import_sdk_core14 = require("@tinycloud/sdk-core");
28007
- var import_sdk_services4 = require("@tinycloud/sdk-services");
28266
+ var import_sdk_services5 = require("@tinycloud/sdk-services");
28008
28267
  var import_contractConstants = require("@tinycloud/sdk-services/kv/replication/contractConstants");
28009
28268
  init_authority();
28010
28269
  var REPLICA_LOGOUT_PENDING_FILE = "replica-logout-pending.json";
@@ -28566,7 +28825,7 @@ function sha256Base32(material) {
28566
28825
  return out.slice(0, 26);
28567
28826
  }
28568
28827
  function partitionDirOf(root, identity) {
28569
- return (0, import_node_path.join)(root, sha256Base32((0, import_sdk_services4.replicationIdentityKey)(identity)));
28828
+ return (0, import_node_path.join)(root, sha256Base32((0, import_sdk_services5.replicationIdentityKey)(identity)));
28570
28829
  }
28571
28830
  function replicaHashOf(prefix, deviceDid) {
28572
28831
  return sha256Base32(`${prefix}
@@ -28640,8 +28899,8 @@ function deviceRecordOf(value) {
28640
28899
  }
28641
28900
  function sameIdentity(a, b) {
28642
28901
  try {
28643
- const left = (0, import_sdk_services4.canonicalReplicationIdentity)(a);
28644
- const right = (0, import_sdk_services4.canonicalReplicationIdentity)(b);
28902
+ const left = (0, import_sdk_services5.canonicalReplicationIdentity)(a);
28903
+ const right = (0, import_sdk_services5.canonicalReplicationIdentity)(b);
28645
28904
  return left.host === right.host && left.space === right.space && left.principal === right.principal;
28646
28905
  } catch {
28647
28906
  return false;
@@ -28657,7 +28916,7 @@ async function ensurePartitionIdentity(dir, identity, dirSync) {
28657
28916
  const file = (0, import_node_path.join)(dir, "identity.json");
28658
28917
  const existing = await readJsonFile(file);
28659
28918
  if (existing === void 0) {
28660
- await writeJsonAtomic(file, { v: 1, ...(0, import_sdk_services4.canonicalReplicationIdentity)(identity) }, dirSync);
28919
+ await writeJsonAtomic(file, { v: 1, ...(0, import_sdk_services5.canonicalReplicationIdentity)(identity) }, dirSync);
28661
28920
  return;
28662
28921
  }
28663
28922
  const record = identityRecordOf(existing);
@@ -28756,7 +29015,7 @@ withGate_fn = async function(section) {
28756
29015
  if (snapshot === void 0) throw gated("gate_unavailable");
28757
29016
  const [principalHash, identityKeyHash] = await Promise.all([
28758
29017
  gate.principalHashOf(snapshot.salt, this.identity.principal),
28759
- gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(this.identity))
29018
+ gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(this.identity))
28760
29019
  ]);
28761
29020
  const access = {
28762
29021
  stamp: __privateGet(this, _stamp),
@@ -29027,7 +29286,8 @@ var SqliteReplicaHandle = class {
29027
29286
  options.signal.addEventListener("abort", onAbort, { once: true });
29028
29287
  const engine = new (__privateGet(this, _replica)).Replica({
29029
29288
  store: __privateGet(this, _store),
29030
- transportFor
29289
+ transportFor,
29290
+ ...this.spec.contentFetch === void 0 ? {} : { contentFetch: this.spec.contentFetch }
29031
29291
  });
29032
29292
  const running = engine.sync({ signal: abort.signal });
29033
29293
  const registration = { promise: running, abort: () => abort.abort() };
@@ -29256,7 +29516,7 @@ function createSqliteReplicaStorage(options) {
29256
29516
  if (snapshot === void 0) throw gated("gate_unavailable");
29257
29517
  const [principalHash, identityKeyHash] = await Promise.all([
29258
29518
  gate.principalHashOf(snapshot.salt, principal),
29259
- identity === void 0 ? Promise.resolve(void 0) : gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(identity))
29519
+ identity === void 0 ? Promise.resolve(void 0) : gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(identity))
29260
29520
  ]);
29261
29521
  return profileSection(async () => {
29262
29522
  assertLogoutClear(options.logoutPendingFile);
@@ -29279,7 +29539,7 @@ function createSqliteReplicaStorage(options) {
29279
29539
  if (snapshot === void 0) throw gated("gate_unavailable");
29280
29540
  const [principalHash, identityKeyHash] = await Promise.all([
29281
29541
  gate.principalHashOf(snapshot.salt, spec.identity.principal),
29282
- gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(spec.identity))
29542
+ gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(spec.identity))
29283
29543
  ]);
29284
29544
  const access = {
29285
29545
  stamp: spec.stamp,
@@ -29363,7 +29623,7 @@ function createSqliteReplicaStorage(options) {
29363
29623
  const identity = identityRecordOf(await readJsonFile((0, import_node_path.join)(idDir, "identity.json")));
29364
29624
  if (identity === void 0) throw new ReplicaStorageError("STORAGE_ERROR", `Missing identity at ${idDir}.`);
29365
29625
  if (await gate.principalHashOf(salt, identity.principal) !== principalHash) continue;
29366
- const identityKeyHash = await gate.identityHashOf(salt, (0, import_sdk_services4.replicationIdentityKey)(identity));
29626
+ const identityKeyHash = await gate.identityHashOf(salt, (0, import_sdk_services5.replicationIdentityKey)(identity));
29367
29627
  const recordedAt = Date.now();
29368
29628
  if (await pathExists((0, import_node_path.join)(idDir, "pending.json")) || await pathExists((0, import_node_path.join)(idDir, "device.jwk"))) {
29369
29629
  intents.push({ id: `pending:${identityKeyHash}`, kind: "pending", identityKeyHash, principalHash, recordedAt });
@@ -30041,7 +30301,7 @@ function createSqliteReplicaStorage(options) {
30041
30301
  for (const dir of await identityDirs()) {
30042
30302
  const identity = identityRecordOf(await readJsonFile((0, import_node_path.join)(dir, "identity.json")));
30043
30303
  if (identity === void 0) throw new ReplicaStorageError("STORAGE_ERROR", `Missing identity at ${dir}.`);
30044
- const hash3 = await gate.identityHashOf(state.salt, (0, import_sdk_services4.replicationIdentityKey)(identity));
30304
+ const hash3 = await gate.identityHashOf(state.salt, (0, import_sdk_services5.replicationIdentityKey)(identity));
30045
30305
  if (intent.kind === "pending" ? hash3 === intent.identityKeyHash : intent.partitionId.startsWith(`${hash3}:`)) {
30046
30306
  idDir = dir;
30047
30307
  break;
@@ -30187,7 +30447,7 @@ function createSqliteReplicaStorage(options) {
30187
30447
  const gate = await fileGate();
30188
30448
  const snapshot = await gate.readGateSnapshot(profileDir);
30189
30449
  if (snapshot === void 0) throw gated("gate_unavailable");
30190
- const identityKeyHash = await gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(spec.identity));
30450
+ const identityKeyHash = await gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(spec.identity));
30191
30451
  const resolvePolicy = async (at, recordFirst) => {
30192
30452
  const policy = spec.grantPolicy;
30193
30453
  if (!Number.isSafeInteger(policy.grantTtlMs) || policy.grantTtlMs <= 0 || policy.grantTtlMs > import_contractConstants.MAX_GRANT_TTL_MS) {
@@ -30209,7 +30469,7 @@ function createSqliteReplicaStorage(options) {
30209
30469
  let partitionId;
30210
30470
  const storeGuard = (section) => guarded(spec.identity.principal, spec.stamp, spec.identity, partitionId, section);
30211
30471
  return guarded(spec.identity.principal, spec.stamp, spec.identity, void 0, async () => {
30212
- if ((0, import_sdk_services4.requiresSecretsOptIn)(spec.space, spec.prefix) && spec.allowSecrets !== true) {
30472
+ if ((0, import_sdk_services5.requiresSecretsOptIn)(spec.space, spec.prefix) && spec.allowSecrets !== true) {
30213
30473
  throw new replica.ReplicaError(
30214
30474
  replica.ReplicaErrorCode.SECRETS_OPT_IN_REQUIRED,
30215
30475
  `Replicating ${spec.space}/kv/${spec.prefix} copies secret material to disk; pass allowSecrets to opt in.`
@@ -30455,7 +30715,7 @@ function createSqliteReplicaStorage(options) {
30455
30715
  const gate = await fileGate();
30456
30716
  const currentGate = await gate.readGateSnapshot(profileDir);
30457
30717
  if (currentGate !== void 0 && currentGate.principalHash !== null && currentGate.principalHash === await gate.principalHashOf(currentGate.salt, target.identity.principal)) {
30458
- const identityKeyHash = await gate.identityHashOf(currentGate.salt, (0, import_sdk_services4.replicationIdentityKey)(target.identity));
30718
+ const identityKeyHash = await gate.identityHashOf(currentGate.salt, (0, import_sdk_services5.replicationIdentityKey)(target.identity));
30459
30719
  await retainGrantInstalls(gate, currentGate, identityKeyHash, store);
30460
30720
  }
30461
30721
  if (state.revoked === null) {