@tinycloud/node-sdk 3.8.0-beta.2 → 3.8.0-beta.21

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 (44) hide show
  1. package/README.md +6 -0
  2. package/dist/{TinyCloudNode-B6VOBrCb.d.ts → TinyCloudNode-DaD5vJhL.d.cts} +49 -6
  3. package/dist/{TinyCloudNode-CVO41O12.d.cts → TinyCloudNode-k3TbBwwj.d.ts} +49 -6
  4. package/dist/{WasmKeyProvider-5Ee7fcAw.d.cts → WasmKeyProvider-BKCPRC-c.d.cts} +21 -1
  5. package/dist/{WasmKeyProvider-5Ee7fcAw.d.ts → WasmKeyProvider-BKCPRC-c.d.ts} +21 -1
  6. package/dist/{authority-DGi1hIMK.d.ts → authority-C_fWaxAu.d.ts} +1 -1
  7. package/dist/{authority-DgVV80bI.d.cts → authority-DJdbZM6k.d.cts} +1 -1
  8. package/dist/core.cjs +375 -40
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/core.d.cts +5 -4
  11. package/dist/core.d.ts +5 -4
  12. package/dist/core.js +370 -35
  13. package/dist/core.js.map +1 -1
  14. package/dist/credentials-core.d.cts +1 -1
  15. package/dist/credentials-core.d.ts +1 -1
  16. package/dist/credentials.d.cts +1 -1
  17. package/dist/credentials.d.ts +1 -1
  18. package/dist/{delegation-0LKZkmP2.d.cts → delegation-Ckc3UETl.d.cts} +1 -1
  19. package/dist/{delegation-0LKZkmP2.d.ts → delegation-Ckc3UETl.d.ts} +1 -1
  20. package/dist/encryption-keys.d.cts +2 -2
  21. package/dist/encryption-keys.d.ts +2 -2
  22. package/dist/index.cjs +343 -51
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +4 -3
  25. package/dist/index.d.ts +4 -3
  26. package/dist/index.js +333 -38
  27. package/dist/index.js.map +1 -1
  28. package/dist/replication/authority.cjs +3 -0
  29. package/dist/replication/authority.cjs.map +1 -1
  30. package/dist/replication/authority.d.cts +4 -3
  31. package/dist/replication/authority.d.ts +4 -3
  32. package/dist/replication/authority.js +3 -0
  33. package/dist/replication/authority.js.map +1 -1
  34. package/dist/replication/runtime.cjs +1 -0
  35. package/dist/replication/runtime.cjs.map +1 -1
  36. package/dist/replication/runtime.js +1 -0
  37. package/dist/replication/runtime.js.map +1 -1
  38. package/dist/share-receive.cjs +775 -0
  39. package/dist/share-receive.cjs.map +1 -0
  40. package/dist/share-receive.d.cts +32 -0
  41. package/dist/share-receive.d.ts +32 -0
  42. package/dist/share-receive.js +777 -0
  43. package/dist/share-receive.js.map +1 -0
  44. package/package.json +11 -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
  }
@@ -17964,6 +17968,7 @@ __export(src_exports, {
17964
17968
  hexDecode: () => import_sdk_core22.hexDecode,
17965
17969
  hexEncode: () => import_sdk_core22.hexEncode,
17966
17970
  installTinyCloudDebugGlobals: () => import_sdk_core16.installTinyCloudDebugGlobals,
17971
+ isAppKeyHostBound: () => isAppKeyHostBound,
17967
17972
  isCapabilitySubset: () => import_sdk_core17.isCapabilitySubset,
17968
17973
  isEvmAddress: () => import_sdk_core16.isEvmAddress,
17969
17974
  isNetworkId: () => import_sdk_core22.isNetworkId,
@@ -18809,6 +18814,7 @@ function deserializeDelegation(data) {
18809
18814
 
18810
18815
  // src/TinyCloudNode.ts
18811
18816
  var import_siwe2 = require("siwe");
18817
+ var import_share_envelope = require("@tinycloud/share-envelope");
18812
18818
 
18813
18819
  // src/replication/module-registry.ts
18814
18820
  var nodeLoaders;
@@ -18824,13 +18830,54 @@ function getNodeReplicationLoaders() {
18824
18830
 
18825
18831
  // src/TinyCloudNode.ts
18826
18832
  var import_sdk_core10 = require("@tinycloud/sdk-core");
18827
- var import_sdk_services3 = require("@tinycloud/sdk-services");
18833
+ var import_sdk_services4 = require("@tinycloud/sdk-services");
18828
18834
  var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
18835
+ var import_resource2 = require("@tinycloud/sdk-services/encryption/resource");
18829
18836
 
18830
18837
  // src/authorization/NodeUserAuthorization.ts
18831
18838
  var import_node_crypto = require("crypto");
18832
18839
  var import_siwe = require("siwe");
18833
18840
  var import_sdk_core5 = require("@tinycloud/sdk-core");
18841
+ var import_resource = require("@tinycloud/sdk-services/encryption/resource");
18842
+ var import_sdk_services3 = require("@tinycloud/sdk-services");
18843
+
18844
+ // src/authorization/appKeyHostBinding.ts
18845
+ var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
18846
+ var IPV4 = /^\d+\.\d+\.\d+\.\d+$/;
18847
+ var APP_ID = /^[a-z0-9][a-z0-9._-]{0,63}$/;
18848
+ function appKeyHostBinding(domain, appId) {
18849
+ if (!APP_ID.test(appId)) return false;
18850
+ if (domain.endsWith(":")) return false;
18851
+ let url;
18852
+ try {
18853
+ url = new URL(`https://${domain}`);
18854
+ } catch {
18855
+ return false;
18856
+ }
18857
+ if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return false;
18858
+ if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return false;
18859
+ const host = url.hostname;
18860
+ if (host.startsWith("[") || host.endsWith(".") || IPV4.test(host)) return false;
18861
+ const labels = host.split(".");
18862
+ if (labels.length < 2 || labels[labels.length - 1] === "localhost") return false;
18863
+ if (!labels.every((label) => DOMAIN_LABEL.test(label))) return false;
18864
+ const reversed = labels.reverse().join(".");
18865
+ return appId === reversed || appId.startsWith(`${reversed}.`);
18866
+ }
18867
+ function isAppKeyHostBound(keysAppId, origin) {
18868
+ if (typeof origin === "string" && !origin.includes("://")) {
18869
+ return appKeyHostBinding(origin, keysAppId);
18870
+ }
18871
+ let url;
18872
+ try {
18873
+ url = typeof origin === "string" ? new URL(origin) : origin;
18874
+ } catch {
18875
+ return false;
18876
+ }
18877
+ return appKeyHostBinding(url.host, keysAppId);
18878
+ }
18879
+
18880
+ // src/authorization/NodeUserAuthorization.ts
18834
18881
  init_authority_sign_in();
18835
18882
 
18836
18883
  // src/authorization/caveatRefusal.ts
@@ -18986,6 +19033,7 @@ function cloneAbilitiesMap(actions) {
18986
19033
  var NodeUserAuthorization = class {
18987
19034
  constructor(config) {
18988
19035
  this.activeSessionKeyId = "default";
19036
+ this.preparedSessionCounter = 0;
18989
19037
  /** Incremented by resetSession; a sign-in started earlier publishes nothing. */
18990
19038
  this.sessionGeneration = 0;
18991
19039
  /**
@@ -19342,6 +19390,23 @@ var NodeUserAuthorization = class {
19342
19390
  );
19343
19391
  return this._capabilityRequest;
19344
19392
  }
19393
+ async refreshNamedKeyFeatureForSignIn(address, chainId) {
19394
+ const request = this.getCapabilityRequest();
19395
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19396
+ const hasNamedKeys = request?.manifests.some((manifest) => manifest.keys?.length && appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id));
19397
+ if (hasNamedKeys) {
19398
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
19399
+ await this.refreshNodeInfo();
19400
+ }
19401
+ }
19402
+ /** Manifest key paths that this sign-in cannot grant. */
19403
+ get unavailableKeyPaths() {
19404
+ const request = this.getCapabilityRequest();
19405
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19406
+ return (request?.manifests ?? []).flatMap(
19407
+ (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)
19408
+ );
19409
+ }
19345
19410
  resolveSpaceName(space, address, chainId) {
19346
19411
  if (space.startsWith("tinycloud:")) {
19347
19412
  return space;
@@ -19533,15 +19598,32 @@ var NodeUserAuthorization = class {
19533
19598
  }
19534
19599
  const rawAbilities = {};
19535
19600
  const currentDid = (0, import_sdk_core5.pkhDid)(address, chainId);
19601
+ if (this._nodeFeatures.includes("encryption-named-keys-v1")) {
19602
+ const siweDomain = this.siweConfig?.domain ?? this.domain;
19603
+ for (const manifest of request.manifests) {
19604
+ if (!appKeyHostBinding(siweDomain, manifest.keys_app_id ?? manifest.app_id)) continue;
19605
+ for (const declaration of manifest.keys ?? []) {
19606
+ const path = "path" in declaration ? declaration.path : declaration.prefix;
19607
+ const resource = `urn:tinycloud:encryption:${currentDid}:default/${path}`;
19608
+ for (const ability of declaration.abilities) {
19609
+ addRawAbility(rawAbilities, resource, `tinycloud.encryption/${ability}`);
19610
+ }
19611
+ }
19612
+ }
19613
+ }
19536
19614
  const spaceResources = request.resources.filter((entry) => {
19537
19615
  if (entry.service !== import_sdk_core5.ENCRYPTION_PERMISSION_SERVICE) {
19538
19616
  return true;
19539
19617
  }
19618
+ const parsed = entry.path.includes("/") ? (0, import_resource.parseEncryptionResource)(entry.path) : { ...(0, import_sdk_services3.parseNetworkId)(entry.path), kind: "network" };
19619
+ const sharePrefix = parsed.kind === "prefix" && parsed.network === "default" && parsed.prefix === "share" && entry.actions.length === 1 && entry.actions[0] === DECRYPT_ACTION && didPrincipalMatches(parsed.ownerDid, currentDid);
19620
+ if (parsed.kind !== "network" && !sharePrefix) {
19621
+ throw new import_sdk_core5.ManifestValidationError("named keys go in manifest.keys");
19622
+ }
19540
19623
  for (const action of entry.actions) {
19541
19624
  addRawAbility(rawAbilities, entry.path, action);
19542
19625
  }
19543
- if (entry.actions.includes(DECRYPT_ACTION)) {
19544
- const parsed = (0, import_sdk_core5.parseNetworkId)(entry.path);
19626
+ if (entry.actions.includes(DECRYPT_ACTION) && parsed.kind === "network") {
19545
19627
  if (didPrincipalMatches(parsed.ownerDid, currentDid)) {
19546
19628
  addRawAbility(rawAbilities, entry.path, NETWORK_CREATE_ACTION);
19547
19629
  }
@@ -19578,8 +19660,7 @@ var NodeUserAuthorization = class {
19578
19660
  /**
19579
19661
  * Build SIWE overrides from the top-level nonce and siweConfig.
19580
19662
  * - Top-level `nonce` is seeded first so `siweConfig.nonce` wins if both are set.
19581
- * - statement is prepended to the default statement
19582
- * - resources are appended to the default resources
19663
+ * - resources are inserted after WASM preparation, before the final ReCap
19583
19664
  * - uri triggers a warning (overwriting delegation target)
19584
19665
  * - all other fields override directly
19585
19666
  * - per-call nonce overrides siweConfig.nonce when provided
@@ -19597,9 +19678,6 @@ var NodeUserAuthorization = class {
19597
19678
  if (statement) {
19598
19679
  overrides.statement = this.statement ? `${statement} ${this.statement}` : statement;
19599
19680
  }
19600
- if (resources && resources.length > 0) {
19601
- overrides.resources = resources;
19602
- }
19603
19681
  if (uri) {
19604
19682
  console.warn(
19605
19683
  "[tinycloud] siweConfig.uri is overwriting the delegation target URI. This may break delegation chain validation if the URI does not match the session key DID."
@@ -19611,6 +19689,29 @@ var NodeUserAuthorization = class {
19611
19689
  }
19612
19690
  return overrides;
19613
19691
  }
19692
+ prepareSessionWithResources(params) {
19693
+ const resources = this.siweConfig?.resources;
19694
+ if (resources?.some((resource) => typeof resource !== "string" || !/^urn:tinycloud:recipients:[A-Za-z0-9_-]+$/.test(resource))) {
19695
+ throw new Error("SIWE metadata resources must be base64url recipients URIs");
19696
+ }
19697
+ const prepared = this.wasm.prepareSession(params);
19698
+ if (!resources?.length) return prepared;
19699
+ const recapLines = /^- urn:recap:[^\r\n]+$/gim;
19700
+ const recaps = prepared.siwe.match(recapLines);
19701
+ const recap = recaps?.[0];
19702
+ if (recaps?.length !== 1 || !prepared.siwe.endsWith(recap)) {
19703
+ throw new Error("Prepared SIWE must end with exactly one ReCap");
19704
+ }
19705
+ const insertedLines = resources.map((resource) => `- ${resource}`);
19706
+ const siwe = prepared.siwe.slice(0, -recap.length) + insertedLines.join("\n") + "\n" + recap;
19707
+ const expectedFields = (0, import_sdk_core5.extractImmutableSiweFields)(prepared.siwe);
19708
+ expectedFields.nonRecapResources = [expectedFields.nonRecapResources, ...insertedLines].filter(Boolean).join("\n");
19709
+ const actualRecaps = siwe.match(recapLines);
19710
+ if (actualRecaps?.length !== 1 || actualRecaps[0] !== recap || (0, import_sdk_core5.diffImmutableSiweFields)(expectedFields, (0, import_sdk_core5.extractImmutableSiweFields)(siwe)).length) {
19711
+ throw new Error("SIWE metadata insertion changed the prepared message");
19712
+ }
19713
+ return { ...prepared, siwe };
19714
+ }
19614
19715
  /**
19615
19716
  * Add an extension to the authorization flow.
19616
19717
  */
@@ -19858,7 +19959,7 @@ var NodeUserAuthorization = class {
19858
19959
  spaceAbilities[spaceId] = (0, import_sdk_core5.resourceCapabilitiesToAbilitiesMap)(resources);
19859
19960
  }
19860
19961
  const abilities = spaceAbilities[options.spaceId] ?? (0, import_sdk_core5.resourceCapabilitiesToAbilitiesMap)([]);
19861
- const prepared = this.wasm.prepareSession({
19962
+ const prepared = this.prepareSessionWithResources({
19862
19963
  abilities,
19863
19964
  spaceAbilities,
19864
19965
  ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
@@ -19937,8 +20038,10 @@ var NodeUserAuthorization = class {
19937
20038
  this._chainId = chainId;
19938
20039
  await this.resolveTinyCloudHostsForSignIn(address, chainId);
19939
20040
  this.assertSignInCurrent(generation);
19940
- const keyId = `session-${Date.now()}`;
19941
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
20041
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20042
+ this.assertSignInCurrent(generation);
20043
+ const keyId = `session-${Date.now()}-${Math.random().toString(36).slice(2)}`;
20044
+ this.sessionManager.createSessionKey(keyId);
19942
20045
  this.activeSessionKeyId = keyId;
19943
20046
  const jwkString = this.sessionManager.jwk(keyId);
19944
20047
  if (!jwkString) {
@@ -19949,7 +20052,7 @@ var NodeUserAuthorization = class {
19949
20052
  const spaceId = capabilityPlan.spaceId;
19950
20053
  const now = /* @__PURE__ */ new Date();
19951
20054
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
19952
- const prepared = this.wasm.prepareSession({
20055
+ const prepared = this.prepareSessionWithResources({
19953
20056
  abilities: capabilityPlan.abilities,
19954
20057
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
19955
20058
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -20083,9 +20186,9 @@ var NodeUserAuthorization = class {
20083
20186
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20084
20187
  const address = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
20085
20188
  const chainId = await this.signer.getChainId();
20086
- const keyId = `session-${Date.now()}`;
20087
- this.sessionManager.renameSessionKeyId(this.activeSessionKeyId, keyId);
20088
- this.activeSessionKeyId = keyId;
20189
+ await this.refreshNamedKeyFeatureForSignIn(address, chainId);
20190
+ const keyId = `openkey-session-${Date.now()}-${++this.preparedSessionCounter}`;
20191
+ this.sessionManager.createSessionKey(keyId);
20089
20192
  const jwkString = this.sessionManager.jwk(keyId);
20090
20193
  if (!jwkString) {
20091
20194
  throw new Error("Failed to create session key");
@@ -20095,7 +20198,7 @@ var NodeUserAuthorization = class {
20095
20198
  const spaceId = capabilityPlan.spaceId;
20096
20199
  const now = /* @__PURE__ */ new Date();
20097
20200
  const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
20098
- const prepared = this.wasm.prepareSession({
20201
+ const prepared = this.prepareSessionWithResources({
20099
20202
  abilities: capabilityPlan.abilities,
20100
20203
  ...capabilityPlan.spaceAbilities !== void 0 ? { spaceAbilities: capabilityPlan.spaceAbilities } : {},
20101
20204
  ...capabilityPlan.rawAbilities !== void 0 ? { rawAbilities: capabilityPlan.rawAbilities } : {},
@@ -20166,6 +20269,7 @@ var NodeUserAuthorization = class {
20166
20269
  await this.sessionStorage.save(address, persistedData);
20167
20270
  this._session = clientSession;
20168
20271
  this._tinyCloudSession = tinyCloudSession;
20272
+ this.activeSessionKeyId = keyId;
20169
20273
  this._address = address;
20170
20274
  this._chainId = chainId;
20171
20275
  await this.refreshNodeInfo();
@@ -20212,6 +20316,11 @@ var NodeUserAuthorization = class {
20212
20316
  * @param options Optional overrides for the OpenKey request.
20213
20317
  */
20214
20318
  async signInWithOpenKey(authorizeFn, options) {
20319
+ const pending = await this.prepareOpenKeySignIn(options);
20320
+ return this.completeOpenKeySignIn(pending, authorizeFn(pending.request), options);
20321
+ }
20322
+ /** Finish address, host and SIWE preparation before an approval click. */
20323
+ async prepareOpenKeySignIn(options) {
20215
20324
  refuseCaveated(this.getCapabilityRequest()?.manifests);
20216
20325
  if (options?.host !== void 0) {
20217
20326
  const overrideHost = options.host.trim();
@@ -20223,7 +20332,7 @@ var NodeUserAuthorization = class {
20223
20332
  const signingAddress = (0, import_sdk_core5.canonicalizeAddress)(await this.signer.getAddress());
20224
20333
  const signingChainId = await this.signer.getChainId();
20225
20334
  await this.resolveTinyCloudHostsForSignIn(signingAddress, signingChainId);
20226
- const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
20335
+ const { prepared, keyId, jwk, address, chainId } = await this.prepareSessionForSigning();
20227
20336
  const presentation = this.buildPresentationEnvelope(options?.reason);
20228
20337
  const hostHint = this.primaryTinyCloudHost;
20229
20338
  const originalRequest = {
@@ -20235,12 +20344,32 @@ var NodeUserAuthorization = class {
20235
20344
  keyId: options?.openkeyKeyId,
20236
20345
  presentation
20237
20346
  };
20347
+ return {
20348
+ request: originalRequest,
20349
+ prepared,
20350
+ keyId,
20351
+ jwk,
20352
+ address,
20353
+ chainId,
20354
+ generation: this.sessionGeneration,
20355
+ createdAt: Date.now()
20356
+ };
20357
+ }
20358
+ /** Complete a prepared approval. The caller starts authorization before awaiting this method. */
20359
+ async completeOpenKeySignIn(pending, authorization, options) {
20360
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
20361
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
20362
+ }
20363
+ if (pending.generation !== this.sessionGeneration) {
20364
+ throw new Error("Prepared OpenKey sign-in expired after sign-out");
20365
+ }
20366
+ const { request: originalRequest, prepared, keyId, jwk } = pending;
20238
20367
  let result;
20239
20368
  let signed2;
20240
20369
  let signature2;
20241
20370
  for (let attempt = 0; ; ) {
20242
20371
  try {
20243
- result ?? (result = await authorizeFn(originalRequest));
20372
+ result ?? (result = await authorization);
20244
20373
  signed2 = await this.verifyOpenKeyResult(result, prepared);
20245
20374
  signature2 = result.signature;
20246
20375
  break;
@@ -21136,6 +21265,7 @@ var ROOT_DELEGATION_ACTIONS = [
21136
21265
  import_sdk_core10.DUCKDB.ALL
21137
21266
  ];
21138
21267
  var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core10.EXPIRY.SESSION_MS;
21268
+ var OWNER_REVOCATION_WINDOW_MS = 10 * 6e4;
21139
21269
  function unifiedRootAttenuation(capabilities) {
21140
21270
  const attenuation = {};
21141
21271
  for (const capability of capabilities) {
@@ -21633,6 +21763,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21633
21763
  constructor(config = {}) {
21634
21764
  this.signer = null;
21635
21765
  this.auth = null;
21766
+ this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
21636
21767
  this.tc = null;
21637
21768
  this._chainId = 1;
21638
21769
  this.replicaPurgeFailedHandlers = /* @__PURE__ */ new Set();
@@ -21667,6 +21798,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21667
21798
  return void 0;
21668
21799
  }
21669
21800
  };
21801
+ this._keysUnavailable = [];
21670
21802
  this._baseVaults = /* @__PURE__ */ new Map();
21671
21803
  this._baseSecrets = /* @__PURE__ */ new Map();
21672
21804
  this._secrets = /* @__PURE__ */ new Map();
@@ -22494,6 +22626,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22494
22626
  this._hooks = void 0;
22495
22627
  this._vault = void 0;
22496
22628
  this._encryption = void 0;
22629
+ this._namedKeyClient = void 0;
22630
+ this._keysUnavailable = [];
22497
22631
  this._baseVaults.clear();
22498
22632
  this._baseSecrets.clear();
22499
22633
  this._secrets.clear();
@@ -22570,6 +22704,37 @@ var _TinyCloudNode = class _TinyCloudNode {
22570
22704
  }
22571
22705
  await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
22572
22706
  }
22707
+ /** Prepare the OpenKey request before the approval click. */
22708
+ async prepareOpenKeySignIn() {
22709
+ if (!this.signer || !this.tc || !this.auth) {
22710
+ throw new Error("Cannot prepare OpenKey sign-in in session-only mode. Provide a signer in config.");
22711
+ }
22712
+ await this.signOutInFlight;
22713
+ await this.accountRegistryTail;
22714
+ await this.wasmBindings.ensureInitialized?.();
22715
+ return this.auth.prepareOpenKeySignIn();
22716
+ }
22717
+ /** Start OpenKey authorization synchronously, then complete normal session setup. */
22718
+ signInWithPreparedOpenKey(pending, authorizeFn) {
22719
+ if (!this.signer || !this.tc || !this.auth || this.signOutInFlight) {
22720
+ throw new Error("Prepared OpenKey sign-in is unavailable");
22721
+ }
22722
+ if (this.consumedOpenKeyPendings.has(pending)) {
22723
+ throw new Error("Prepared OpenKey sign-in was already used");
22724
+ }
22725
+ if (Date.now() - pending.createdAt > 5 * 6e4) {
22726
+ throw new Error("Prepared OpenKey sign-in expired; prepare again");
22727
+ }
22728
+ this.consumedOpenKeyPendings.add(pending);
22729
+ const authorization = authorizeFn(pending.request);
22730
+ void authorization.catch(() => void 0);
22731
+ return this.signInWith(() => {
22732
+ if (this._address !== pending.address || this._chainId !== pending.chainId) {
22733
+ throw new Error("OpenKey account changed after sign-in preparation");
22734
+ }
22735
+ return this.auth.completeOpenKeySignIn(pending, authorization);
22736
+ });
22737
+ }
22573
22738
  async signInWith(performSignIn) {
22574
22739
  refuseCaveated(this.auth?.capabilityRequest?.manifests);
22575
22740
  const epoch = this.sessionEpoch;
@@ -22616,6 +22781,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22616
22781
  this._hooks = void 0;
22617
22782
  this._vault = void 0;
22618
22783
  this._encryption = void 0;
22784
+ this._namedKeyClient = void 0;
22785
+ this._keysUnavailable = [];
22619
22786
  this._baseVaults.clear();
22620
22787
  this._baseSecrets.clear();
22621
22788
  this._secrets.clear();
@@ -22668,6 +22835,26 @@ var _TinyCloudNode = class _TinyCloudNode {
22668
22835
  this.assertSessionEpoch(epoch, "Sign-in");
22669
22836
  await this.ensureRequestedEncryptionNetworks();
22670
22837
  this.assertSessionEpoch(epoch, "Sign-in");
22838
+ this._keysUnavailable = this.auth?.unavailableKeyPaths ?? [];
22839
+ if (this._keysUnavailable.length > 0) {
22840
+ this.notificationHandler.warning(`Named keys unavailable: ${this._keysUnavailable.join(", ")}`);
22841
+ }
22842
+ if (this.nodeFeatures.includes("encryption-named-keys-v1")) {
22843
+ for (const manifest of this.auth?.capabilityRequest?.manifests ?? []) {
22844
+ for (const declaration of manifest.keys ?? []) {
22845
+ if ("path" in declaration && declaration.abilities.includes("key.create") && !this._keysUnavailable.includes(declaration.path)) {
22846
+ try {
22847
+ await this.keys.ensureKey(declaration.path);
22848
+ } catch (error) {
22849
+ this._keysUnavailable.push(declaration.path);
22850
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z_]+$/.test(error.code) ? error.code : "UNKNOWN";
22851
+ this.notificationHandler.warning(`Named key unavailable: ${declaration.path} (${code})`);
22852
+ }
22853
+ this.assertSessionEpoch(epoch, "Sign-in");
22854
+ }
22855
+ }
22856
+ }
22857
+ }
22671
22858
  if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
22672
22859
  await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(import_sdk_core10.SECRETS_SPACE));
22673
22860
  }
@@ -23244,7 +23431,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23244
23431
  if (!this.currentSessionCoversAccountRegistry()) {
23245
23432
  if (!_TinyCloudNode.accountRegistrySkipLogged) {
23246
23433
  _TinyCloudNode.accountRegistrySkipLogged = true;
23247
- import_sdk_services3.tinyCloudDebugLogger.log("registry.skip");
23434
+ import_sdk_services4.tinyCloudDebugLogger.log("registry.skip");
23248
23435
  }
23249
23436
  return;
23250
23437
  }
@@ -23455,7 +23642,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23455
23642
  const networkIds = /* @__PURE__ */ new Set();
23456
23643
  for (const resource of request.resources) {
23457
23644
  if (resource.service === import_sdk_core10.ENCRYPTION_PERMISSION_SERVICE && resource.path.startsWith("urn:tinycloud:encryption:") && resource.actions.includes(DECRYPT_ACTION2)) {
23458
- networkIds.add(resource.path);
23645
+ const parsed = resource.path.includes("/") ? (0, import_resource2.parseEncryptionResource)(resource.path) : { ...(0, import_sdk_core10.parseNetworkId)(resource.path), kind: "network" };
23646
+ if (parsed.kind === "network") networkIds.add(resource.path);
23459
23647
  }
23460
23648
  }
23461
23649
  return [...networkIds];
@@ -24005,6 +24193,8 @@ var _TinyCloudNode = class _TinyCloudNode {
24005
24193
  this._hooks = stagedGraph.hooks;
24006
24194
  this._vault = stagedGraph.vault;
24007
24195
  this._encryption = stagedGraph.encryption;
24196
+ this._namedKeyClient = void 0;
24197
+ this._keysUnavailable = [];
24008
24198
  this._capabilityRegistry = stagedGraph.capabilityRegistry;
24009
24199
  this._keyProvider = stagedGraph.keyProvider;
24010
24200
  this._sharingService = stagedGraph.sharingService;
@@ -24186,6 +24376,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24186
24376
  invoke: graph.invoke,
24187
24377
  invokeAny: graph.invokeAny,
24188
24378
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
24379
+ sessionInvocationSigner: this.sessionInvocationSignerFor(input.serviceSession),
24189
24380
  fetch: graph.fetch
24190
24381
  });
24191
24382
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -24401,6 +24592,31 @@ var _TinyCloudNode = class _TinyCloudNode {
24401
24592
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
24402
24593
  return signBytesWithJwk(bytes, session.jwk);
24403
24594
  }
24595
+ /**
24596
+ * Signs proofless invocations with `session`'s own did:key, for the node's
24597
+ * issued-delegation list and revocation by the delegator. The audience is
24598
+ * this Node's DID; each invocation lives 60 seconds.
24599
+ */
24600
+ sessionInvocationSignerFor(session) {
24601
+ return {
24602
+ did: () => session.verificationMethod.split("#", 1)[0],
24603
+ sign: async (attenuation) => {
24604
+ const now = Math.floor(Date.now() / 1e3);
24605
+ const invocation = await (0, import_sdk_core10.signCompactUcanAuthorization)({
24606
+ issuerDid: session.verificationMethod.split("#", 1)[0],
24607
+ audienceDid: await this.fetchNodeId(),
24608
+ attenuation,
24609
+ facts: [{}],
24610
+ proofs: [],
24611
+ notBefore: now,
24612
+ expiresAt: now + 60,
24613
+ nonce: `urn:uuid:${crypto.randomUUID()}`,
24614
+ sign: (bytes) => signBytesWithJwk(bytes, session.jwk)
24615
+ });
24616
+ return invocation.authorization;
24617
+ }
24618
+ };
24619
+ }
24404
24620
  /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
24405
24621
  async createUnifiedOwnerRoot(input) {
24406
24622
  const ownerDid = this.credentialHolderDid;
@@ -24914,7 +25130,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24914
25130
  );
24915
25131
  graph.assertActive();
24916
25132
  if (!response.ok) {
24917
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services3.parsePermissionHint)({
25133
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services4.parsePermissionHint)({
24918
25134
  service: "tinycloud.encryption",
24919
25135
  path: networkId,
24920
25136
  actions: ["tinycloud.encryption/decrypt"]
@@ -24935,7 +25151,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24935
25151
  graph.assertActive();
24936
25152
  const signed2 = await this.signRawNetworkAuthorization({
24937
25153
  targetNode: input.targetNode,
24938
- networkId: input.networkId,
25154
+ networkId: input.body.keyPath === void 0 ? input.networkId : `${input.networkId}/${input.body.keyPath}`,
24939
25155
  action: DECRYPT_ACTION2,
24940
25156
  facts: input.facts
24941
25157
  }, { graph, session });
@@ -25108,6 +25324,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25108
25324
  invoke: graph.invoke,
25109
25325
  invokeAny: graph.invokeAny,
25110
25326
  signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
25327
+ sessionInvocationSigner: this.sessionInvocationSignerFor(serviceSession),
25111
25328
  fetch: graph.fetch
25112
25329
  });
25113
25330
  this._spaceService = new import_sdk_core10.SpaceService({
@@ -25358,7 +25575,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25358
25575
  * Sign a wallet (CACAO) revocation of the delegation `cid` for the node's
25359
25576
  * `POST /revoke`, and return its Authorization header value. The node
25360
25577
  * accepts it when the owner delegated `cid` or owns a space its abilities
25361
- * target, whichever key issued it. Valid for 60 seconds. Requires the
25578
+ * target, whichever key issued it. Valid for 10 minutes. Requires the
25362
25579
  * owner wallet signer.
25363
25580
  */
25364
25581
  async signOwnerRevocation(cid) {
@@ -25374,7 +25591,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25374
25591
  chainId: session.chainId,
25375
25592
  nonce: (0, import_siwe2.generateNonce)(),
25376
25593
  issuedAt: issuedAt.toISOString(),
25377
- expirationTime: new Date(issuedAt.getTime() + 6e4).toISOString()
25594
+ expirationTime: new Date(issuedAt.getTime() + OWNER_REVOCATION_WINDOW_MS).toISOString()
25378
25595
  }).prepareMessage();
25379
25596
  const signature2 = await this.signer.signMessage(siwe);
25380
25597
  return this.wasmBindings.siweToDelegationHeaders({ siwe, signature: signature2 }).Authorization;
@@ -25601,6 +25818,40 @@ var _TinyCloudNode = class _TinyCloudNode {
25601
25818
  get encryption() {
25602
25819
  return this.getEncryptionService();
25603
25820
  }
25821
+ /** Named keys bound to the current session. */
25822
+ get keys() {
25823
+ return new Proxy({}, {
25824
+ get: (_target, property) => {
25825
+ if (property === "refreshNodeInfo") {
25826
+ return () => {
25827
+ void this._namedKeyClient?.then((client) => client.refreshNodeInfo());
25828
+ };
25829
+ }
25830
+ if (typeof property !== "string" || ![
25831
+ "getPublicKey",
25832
+ "describeKey",
25833
+ "createKey",
25834
+ "blockKey",
25835
+ "unblockKey",
25836
+ "ensureKey",
25837
+ "encrypt",
25838
+ "decrypt",
25839
+ "decryptMany"
25840
+ ].includes(property)) return void 0;
25841
+ return (...args) => {
25842
+ this._namedKeyClient ?? (this._namedKeyClient = import("./encryption-keys.cjs").then(({ createNamedKeyClient }) => createNamedKeyClient(this)));
25843
+ return this._namedKeyClient.then((client) => {
25844
+ const method = client[property];
25845
+ return method.apply(client, args);
25846
+ });
25847
+ };
25848
+ }
25849
+ });
25850
+ }
25851
+ /** Manifest key paths unavailable during the latest `signIn()` attempt. */
25852
+ get keysUnavailable() {
25853
+ return this._keysUnavailable;
25854
+ }
25604
25855
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
25605
25856
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
25606
25857
  return this.fetchEncryptionNetworkAt(
@@ -25728,7 +25979,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25728
25979
  );
25729
25980
  if (result.status !== "ok") {
25730
25981
  if (result.status === "permission_required") {
25731
- const hint = (0, import_sdk_services3.parsePermissionHint)(result.hint);
25982
+ const hint = (0, import_sdk_services4.parsePermissionHint)(result.hint);
25732
25983
  if (hint === void 0) return { status: "read_failed" };
25733
25984
  return {
25734
25985
  status: "permission_required",
@@ -25857,7 +26108,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25857
26108
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
25858
26109
  return false;
25859
26110
  }
25860
- const expanded = this.expandPermissionEntries(permissions);
26111
+ const expanded = (0, import_sdk_core10.expandRuntimePermissionEntries)(permissions, (0, import_sdk_core10.pkhDid)(session.address, session.chainId));
25861
26112
  if (this.sessionCoversPermissionEntries(session, expanded)) {
25862
26113
  return true;
25863
26114
  }
@@ -25877,7 +26128,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25877
26128
  if (!session || !Array.isArray(permissions) || permissions.length === 0) {
25878
26129
  return [];
25879
26130
  }
25880
- const expanded = this.expandPermissionEntries(permissions);
26131
+ const expanded = (0, import_sdk_core10.expandRuntimePermissionEntries)(permissions, (0, import_sdk_core10.pkhDid)(session.address, session.chainId));
25881
26132
  return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
25882
26133
  (grant) => grant.delegation
25883
26134
  );
@@ -26052,7 +26303,28 @@ var _TinyCloudNode = class _TinyCloudNode {
26052
26303
  throw new import_sdk_core10.SessionExpiredError(sessionExpiry);
26053
26304
  }
26054
26305
  }
26055
- const expanded = this.expandPermissionEntries(permissions);
26306
+ const expanded = (0, import_sdk_core10.expandRuntimePermissionEntries)(permissions, (0, import_sdk_core10.pkhDid)(session.address, session.chainId));
26307
+ const bindsShareKey = expanded.some((entry) => entry.service === import_sdk_core10.ENCRYPTION_PERMISSION_SERVICE && entry.actions.includes("tinycloud.encryption/key.create"));
26308
+ const captured = bindsShareKey ? { ...session, jwk: { ...options?.expectedSessionKey ?? session.jwk } } : session;
26309
+ const assertCapturedKey = bindsShareKey ? () => {
26310
+ const live = session.jwk;
26311
+ const key2 = captured.jwk;
26312
+ if (this.currentTinyCloudSession() !== session || session.verificationMethod !== captured.verificationMethod || session.delegationCid !== captured.delegationCid || session.spaceId !== captured.spaceId || session.address !== captured.address || session.chainId !== captured.chainId || live?.kty !== key2.kty || live?.crv !== key2.crv || live?.x !== key2.x || live?.y !== key2.y || live?.d !== key2.d) {
26313
+ throw new Error("Runtime permission request cancelled because the session key changed.");
26314
+ }
26315
+ } : void 0;
26316
+ const sessionKeyDid = bindsShareKey ? this.sessionDid : void 0;
26317
+ if (bindsShareKey) {
26318
+ const key2 = captured.jwk;
26319
+ if (key2.kty !== "OKP" || key2.crv !== "Ed25519" || typeof key2.x !== "string") {
26320
+ throw new Error("Runtime permission session key does not match its signed identity.");
26321
+ }
26322
+ const keyDid = (0, import_share_envelope.didKeyFromEd25519PublicKey)(base64UrlDecode(key2.x));
26323
+ if (!didPrincipalMatches2(keyDid, sessionKeyDid) || !didPrincipalMatches2(keyDid, captured.verificationMethod) || !didPrincipalMatches2(keyDid, new import_siwe2.SiweMessage(session.siwe).uri)) {
26324
+ throw new Error("Runtime permission session key does not match its signed identity.");
26325
+ }
26326
+ assertCapturedKey();
26327
+ }
26056
26328
  if (this.sessionCoversPermissionEntries(session, expanded)) {
26057
26329
  return [];
26058
26330
  }
@@ -26102,13 +26374,13 @@ var _TinyCloudNode = class _TinyCloudNode {
26102
26374
  return this.wasmBindings.prepareSession({
26103
26375
  abilities,
26104
26376
  ...rawForDelegation.length > 0 ? { rawAbilities: this.permissionsToRawAbilities(rawForDelegation) } : {},
26105
- address: this.wasmBindings.ensureEip55(session.address),
26106
- chainId: session.chainId,
26377
+ address: this.wasmBindings.ensureEip55(captured.address),
26378
+ chainId: captured.chainId,
26107
26379
  domain: this.siweDomain,
26108
26380
  issuedAt: now.toISOString(),
26109
26381
  expirationTime: expiresAt.toISOString(),
26110
26382
  spaceId,
26111
- jwk: session.jwk
26383
+ jwk: captured.jwk
26112
26384
  });
26113
26385
  } catch (error) {
26114
26386
  if (rawForDelegation.some((entry) => this.isRawRevocationPermissionEntry(entry)) && /invalid ReCap resource URI/i.test(error instanceof Error ? error.message : String(error))) {
@@ -26120,8 +26392,13 @@ var _TinyCloudNode = class _TinyCloudNode {
26120
26392
  throw error;
26121
26393
  }
26122
26394
  })();
26395
+ if (bindsShareKey && !didPrincipalMatches2(new import_siwe2.SiweMessage(prepared.siwe).uri, sessionKeyDid)) {
26396
+ throw new Error("Prepared runtime permission targets a different session key.");
26397
+ }
26398
+ assertCapturedKey?.();
26123
26399
  const signature2 = await this.signer.signMessage(prepared.siwe);
26124
26400
  graph.assertActive();
26401
+ assertCapturedKey?.();
26125
26402
  const delegatedSession = this.wasmBindings.completeSessionSetup({
26126
26403
  ...prepared,
26127
26404
  signature: signature2
@@ -26150,18 +26427,19 @@ var _TinyCloudNode = class _TinyCloudNode {
26150
26427
  delegatedSession,
26151
26428
  delegatedEntries,
26152
26429
  spaceId,
26153
- session,
26430
+ captured,
26154
26431
  expiresAt,
26155
26432
  { siwe: prepared.siwe, signature: signature2 }
26156
26433
  );
26157
26434
  graph.assertActive();
26435
+ assertCapturedKey?.();
26158
26436
  this.runtimePermissionGrants.push({
26159
26437
  session: {
26160
26438
  delegationHeader: delegatedSession.delegationHeader,
26161
26439
  delegationCid: delegatedSession.delegationCid,
26162
26440
  spaceId,
26163
- verificationMethod: session.verificationMethod,
26164
- jwk: session.jwk
26441
+ verificationMethod: captured.verificationMethod,
26442
+ jwk: captured.jwk
26165
26443
  },
26166
26444
  delegation,
26167
26445
  operations: this.permissionOperations(delegatedEntries, spaceId),
@@ -26490,7 +26768,19 @@ var _TinyCloudNode = class _TinyCloudNode {
26490
26768
  "delegateTo requires a non-empty permissions array"
26491
26769
  );
26492
26770
  }
26493
- const expandedEntries = this.expandPermissionEntries(permissions);
26771
+ const expandedEntries = (0, import_sdk_core10.expandDelegationPermissionEntries)(permissions);
26772
+ const namedKeyEntries = expandedEntries.filter((entry) => entry.service === import_sdk_core10.ENCRYPTION_PERMISSION_SERVICE && entry.path.includes("/"));
26773
+ if (namedKeyEntries.length > 0) {
26774
+ if (expandedEntries.some((entry) => entry.service === import_sdk_core10.ENCRYPTION_PERMISSION_SERVICE && !entry.path.includes("/"))) {
26775
+ throw new import_sdk_core10.ManifestValidationError("named-key delegation cannot include bare-network permissions");
26776
+ }
26777
+ if (options?.forceWalletSign) {
26778
+ throw new import_sdk_core10.ManifestValidationError("named-key delegation requires a signed session grant");
26779
+ }
26780
+ const granted = this.projectSignedRecapCapabilities(session.siwe);
26781
+ const { subset, missing } = this.signedCapabilitySubset(namedKeyEntries, granted);
26782
+ if (!subset) throw new import_sdk_core10.PermissionNotInManifestError(missing, granted);
26783
+ }
26494
26784
  const now = /* @__PURE__ */ new Date();
26495
26785
  const expiryMs = resolveExpiryMs(options?.expiry);
26496
26786
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -28009,7 +28299,7 @@ var FileSessionStorage = class {
28009
28299
  };
28010
28300
 
28011
28301
  // src/authorization/openKeyBridge.ts
28012
- function wireOpenKeyAuthorize(openkey) {
28302
+ function wireOpenKeyAuthorize(openkey, opts) {
28013
28303
  return async (input) => {
28014
28304
  const result = await openkey.authorizeTinyCloud({
28015
28305
  protocolVersion: 1,
@@ -28020,7 +28310,7 @@ function wireOpenKeyAuthorize(openkey) {
28020
28310
  // Forward the presentation envelope verbatim — the OpenKey SDK
28021
28311
  // validates and size-bounds it before use.
28022
28312
  presentation: input.presentation
28023
- });
28313
+ }, opts);
28024
28314
  if (result.protocolVersion !== 1) {
28025
28315
  throw new Error(
28026
28316
  `wireOpenKeyAuthorize: OpenKey returned unsupported protocolVersion ${result.protocolVersion}`
@@ -28073,7 +28363,7 @@ var import_node_fs = require("fs");
28073
28363
  var import_promises = require("fs/promises");
28074
28364
  var import_node_path = require("path");
28075
28365
  var import_sdk_core14 = require("@tinycloud/sdk-core");
28076
- var import_sdk_services4 = require("@tinycloud/sdk-services");
28366
+ var import_sdk_services5 = require("@tinycloud/sdk-services");
28077
28367
  var import_contractConstants = require("@tinycloud/sdk-services/kv/replication/contractConstants");
28078
28368
  init_authority();
28079
28369
  var REPLICA_LOGOUT_PENDING_FILE = "replica-logout-pending.json";
@@ -28635,7 +28925,7 @@ function sha256Base32(material) {
28635
28925
  return out.slice(0, 26);
28636
28926
  }
28637
28927
  function partitionDirOf(root, identity) {
28638
- return (0, import_node_path.join)(root, sha256Base32((0, import_sdk_services4.replicationIdentityKey)(identity)));
28928
+ return (0, import_node_path.join)(root, sha256Base32((0, import_sdk_services5.replicationIdentityKey)(identity)));
28639
28929
  }
28640
28930
  function replicaHashOf(prefix, deviceDid) {
28641
28931
  return sha256Base32(`${prefix}
@@ -28709,8 +28999,8 @@ function deviceRecordOf(value) {
28709
28999
  }
28710
29000
  function sameIdentity(a, b) {
28711
29001
  try {
28712
- const left = (0, import_sdk_services4.canonicalReplicationIdentity)(a);
28713
- const right = (0, import_sdk_services4.canonicalReplicationIdentity)(b);
29002
+ const left = (0, import_sdk_services5.canonicalReplicationIdentity)(a);
29003
+ const right = (0, import_sdk_services5.canonicalReplicationIdentity)(b);
28714
29004
  return left.host === right.host && left.space === right.space && left.principal === right.principal;
28715
29005
  } catch {
28716
29006
  return false;
@@ -28726,7 +29016,7 @@ async function ensurePartitionIdentity(dir, identity, dirSync) {
28726
29016
  const file = (0, import_node_path.join)(dir, "identity.json");
28727
29017
  const existing = await readJsonFile(file);
28728
29018
  if (existing === void 0) {
28729
- await writeJsonAtomic(file, { v: 1, ...(0, import_sdk_services4.canonicalReplicationIdentity)(identity) }, dirSync);
29019
+ await writeJsonAtomic(file, { v: 1, ...(0, import_sdk_services5.canonicalReplicationIdentity)(identity) }, dirSync);
28730
29020
  return;
28731
29021
  }
28732
29022
  const record = identityRecordOf(existing);
@@ -28825,7 +29115,7 @@ withGate_fn = async function(section) {
28825
29115
  if (snapshot === void 0) throw gated("gate_unavailable");
28826
29116
  const [principalHash, identityKeyHash] = await Promise.all([
28827
29117
  gate.principalHashOf(snapshot.salt, this.identity.principal),
28828
- gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(this.identity))
29118
+ gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(this.identity))
28829
29119
  ]);
28830
29120
  const access = {
28831
29121
  stamp: __privateGet(this, _stamp),
@@ -29096,7 +29386,8 @@ var SqliteReplicaHandle = class {
29096
29386
  options.signal.addEventListener("abort", onAbort, { once: true });
29097
29387
  const engine = new (__privateGet(this, _replica)).Replica({
29098
29388
  store: __privateGet(this, _store),
29099
- transportFor
29389
+ transportFor,
29390
+ ...this.spec.contentFetch === void 0 ? {} : { contentFetch: this.spec.contentFetch }
29100
29391
  });
29101
29392
  const running = engine.sync({ signal: abort.signal });
29102
29393
  const registration = { promise: running, abort: () => abort.abort() };
@@ -29325,7 +29616,7 @@ function createSqliteReplicaStorage(options) {
29325
29616
  if (snapshot === void 0) throw gated("gate_unavailable");
29326
29617
  const [principalHash, identityKeyHash] = await Promise.all([
29327
29618
  gate.principalHashOf(snapshot.salt, principal),
29328
- identity === void 0 ? Promise.resolve(void 0) : gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(identity))
29619
+ identity === void 0 ? Promise.resolve(void 0) : gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(identity))
29329
29620
  ]);
29330
29621
  return profileSection(async () => {
29331
29622
  assertLogoutClear(options.logoutPendingFile);
@@ -29348,7 +29639,7 @@ function createSqliteReplicaStorage(options) {
29348
29639
  if (snapshot === void 0) throw gated("gate_unavailable");
29349
29640
  const [principalHash, identityKeyHash] = await Promise.all([
29350
29641
  gate.principalHashOf(snapshot.salt, spec.identity.principal),
29351
- gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(spec.identity))
29642
+ gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(spec.identity))
29352
29643
  ]);
29353
29644
  const access = {
29354
29645
  stamp: spec.stamp,
@@ -29432,7 +29723,7 @@ function createSqliteReplicaStorage(options) {
29432
29723
  const identity = identityRecordOf(await readJsonFile((0, import_node_path.join)(idDir, "identity.json")));
29433
29724
  if (identity === void 0) throw new ReplicaStorageError("STORAGE_ERROR", `Missing identity at ${idDir}.`);
29434
29725
  if (await gate.principalHashOf(salt, identity.principal) !== principalHash) continue;
29435
- const identityKeyHash = await gate.identityHashOf(salt, (0, import_sdk_services4.replicationIdentityKey)(identity));
29726
+ const identityKeyHash = await gate.identityHashOf(salt, (0, import_sdk_services5.replicationIdentityKey)(identity));
29436
29727
  const recordedAt = Date.now();
29437
29728
  if (await pathExists((0, import_node_path.join)(idDir, "pending.json")) || await pathExists((0, import_node_path.join)(idDir, "device.jwk"))) {
29438
29729
  intents.push({ id: `pending:${identityKeyHash}`, kind: "pending", identityKeyHash, principalHash, recordedAt });
@@ -30110,7 +30401,7 @@ function createSqliteReplicaStorage(options) {
30110
30401
  for (const dir of await identityDirs()) {
30111
30402
  const identity = identityRecordOf(await readJsonFile((0, import_node_path.join)(dir, "identity.json")));
30112
30403
  if (identity === void 0) throw new ReplicaStorageError("STORAGE_ERROR", `Missing identity at ${dir}.`);
30113
- const hash3 = await gate.identityHashOf(state.salt, (0, import_sdk_services4.replicationIdentityKey)(identity));
30404
+ const hash3 = await gate.identityHashOf(state.salt, (0, import_sdk_services5.replicationIdentityKey)(identity));
30114
30405
  if (intent.kind === "pending" ? hash3 === intent.identityKeyHash : intent.partitionId.startsWith(`${hash3}:`)) {
30115
30406
  idDir = dir;
30116
30407
  break;
@@ -30256,7 +30547,7 @@ function createSqliteReplicaStorage(options) {
30256
30547
  const gate = await fileGate();
30257
30548
  const snapshot = await gate.readGateSnapshot(profileDir);
30258
30549
  if (snapshot === void 0) throw gated("gate_unavailable");
30259
- const identityKeyHash = await gate.identityHashOf(snapshot.salt, (0, import_sdk_services4.replicationIdentityKey)(spec.identity));
30550
+ const identityKeyHash = await gate.identityHashOf(snapshot.salt, (0, import_sdk_services5.replicationIdentityKey)(spec.identity));
30260
30551
  const resolvePolicy = async (at, recordFirst) => {
30261
30552
  const policy = spec.grantPolicy;
30262
30553
  if (!Number.isSafeInteger(policy.grantTtlMs) || policy.grantTtlMs <= 0 || policy.grantTtlMs > import_contractConstants.MAX_GRANT_TTL_MS) {
@@ -30278,7 +30569,7 @@ function createSqliteReplicaStorage(options) {
30278
30569
  let partitionId;
30279
30570
  const storeGuard = (section) => guarded(spec.identity.principal, spec.stamp, spec.identity, partitionId, section);
30280
30571
  return guarded(spec.identity.principal, spec.stamp, spec.identity, void 0, async () => {
30281
- if ((0, import_sdk_services4.requiresSecretsOptIn)(spec.space, spec.prefix) && spec.allowSecrets !== true) {
30572
+ if ((0, import_sdk_services5.requiresSecretsOptIn)(spec.space, spec.prefix) && spec.allowSecrets !== true) {
30282
30573
  throw new replica.ReplicaError(
30283
30574
  replica.ReplicaErrorCode.SECRETS_OPT_IN_REQUIRED,
30284
30575
  `Replicating ${spec.space}/kv/${spec.prefix} copies secret material to disk; pass allowSecrets to opt in.`
@@ -30524,7 +30815,7 @@ function createSqliteReplicaStorage(options) {
30524
30815
  const gate = await fileGate();
30525
30816
  const currentGate = await gate.readGateSnapshot(profileDir);
30526
30817
  if (currentGate !== void 0 && currentGate.principalHash !== null && currentGate.principalHash === await gate.principalHashOf(currentGate.salt, target.identity.principal)) {
30527
- const identityKeyHash = await gate.identityHashOf(currentGate.salt, (0, import_sdk_services4.replicationIdentityKey)(target.identity));
30818
+ const identityKeyHash = await gate.identityHashOf(currentGate.salt, (0, import_sdk_services5.replicationIdentityKey)(target.identity));
30528
30819
  await retainGrantInstalls(gate, currentGate, identityKeyHash, store);
30529
30820
  }
30530
30821
  if (state.revoked === null) {
@@ -30713,6 +31004,7 @@ function sqliteReplicaStorage(options) {
30713
31004
  hexDecode,
30714
31005
  hexEncode,
30715
31006
  installTinyCloudDebugGlobals,
31007
+ isAppKeyHostBound,
30716
31008
  isCapabilitySubset,
30717
31009
  isEvmAddress,
30718
31010
  isNetworkId,