@tinycloud/node-sdk 3.12.0-beta.2 → 3.12.0-beta.4

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 (32) hide show
  1. package/README.md +39 -0
  2. package/dist/{NodeUserAuthorization-B05Wjjpb.d.cts → NodeUserAuthorization-B_TFUlua.d.cts} +18 -52
  3. package/dist/{NodeUserAuthorization-B05Wjjpb.d.ts → NodeUserAuthorization-B_TFUlua.d.ts} +18 -52
  4. package/dist/{TinyCloudNode-CEFGtSWn.d.ts → TinyCloudNode-Brcu8QoX.d.cts} +31 -59
  5. package/dist/{TinyCloudNode-6_IumWu0.d.cts → TinyCloudNode-DBJUnsMN.d.ts} +31 -59
  6. package/dist/{authority-DXpnuId1.d.ts → authority-BFc5lesl.d.ts} +1 -1
  7. package/dist/{authority-DODDXxoP.d.cts → authority-DQ6R1HEG.d.cts} +1 -1
  8. package/dist/core.cjs +499 -283
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/core.d.cts +5 -5
  11. package/dist/core.d.ts +5 -5
  12. package/dist/core.js +525 -287
  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-BQbAuKad.d.cts → delegation-CeOWDnBB.d.cts} +7 -0
  19. package/dist/{delegation-BQbAuKad.d.ts → delegation-CeOWDnBB.d.ts} +7 -0
  20. package/dist/encryption-keys.d.cts +3 -3
  21. package/dist/encryption-keys.d.ts +3 -3
  22. package/dist/index.cjs +516 -300
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +5 -4
  25. package/dist/index.d.ts +5 -4
  26. package/dist/index.js +526 -288
  27. package/dist/index.js.map +1 -1
  28. package/dist/replication/authority.d.cts +4 -4
  29. package/dist/replication/authority.d.ts +4 -4
  30. package/dist/space-hosting.d.cts +1 -1
  31. package/dist/space-hosting.d.ts +1 -1
  32. package/package.json +4 -4
package/dist/core.cjs CHANGED
@@ -54,7 +54,10 @@ __export(core_exports, {
54
54
  KVService: () => import_sdk_core15.KVService,
55
55
  ManifestValidationError: () => import_sdk_core14.ManifestValidationError,
56
56
  MemorySessionStorage: () => MemorySessionStorage,
57
+ NodeIdentityMismatchError: () => import_sdk_services7.NodeIdentityMismatchError,
58
+ NodeNotRespondingError: () => import_sdk_services7.NodeNotRespondingError,
57
59
  NodeUserAuthorization: () => NodeUserAuthorization,
60
+ PartialNodeWriteError: () => import_sdk_services7.PartialNodeWriteError,
58
61
  PermissionNotInManifestError: () => import_sdk_core14.PermissionNotInManifestError,
59
62
  PrefixedKVService: () => import_sdk_core15.PrefixedKVService,
60
63
  ProtocolMismatchError: () => import_sdk_core22.ProtocolMismatchError,
@@ -138,6 +141,7 @@ __export(core_exports, {
138
141
  });
139
142
  module.exports = __toCommonJS(core_exports);
140
143
  var import_sdk_core12 = require("@tinycloud/sdk-core");
144
+ var import_sdk_services7 = require("@tinycloud/sdk-services");
141
145
 
142
146
  // src/replication/module-registry.ts
143
147
  var nodeLoaders;
@@ -609,11 +613,12 @@ var NodeUserAuthorization = class {
609
613
  this.tinycloudHosts = config.tinycloudHosts;
610
614
  this.tinycloudRegistryUrl = config.tinycloudRegistryUrl;
611
615
  this.tinycloudFallbackHosts = config.tinycloudFallbackHosts;
612
- this.registryPolicy = config.registryPolicy;
613
616
  this.newUserHost = config.newUserHost;
614
617
  this.registryChainId = config.registryChainId;
615
618
  this.registrySequenceStore = config.registrySequenceStore ?? (0, import_sdk_core5.createInMemoryRegistrySequenceStore)();
619
+ this.registryCacheStore = config.registryCacheStore ?? (0, import_sdk_core5.createInMemoryRegistryCacheStore)();
616
620
  this.onNodeNotFound = config.onNodeNotFound;
621
+ this.onRegistryFallback = config.onRegistryFallback;
617
622
  this.autoDiscoverLocalNode = config.autoDiscoverLocalNode;
618
623
  this.localNodeUrl = config.localNodeUrl;
619
624
  this.localLinkName = config.localLinkName;
@@ -681,13 +686,8 @@ var NodeUserAuthorization = class {
681
686
  * undefined for missing SIWEs and the SDK then treats the session as
682
687
  * expired-at-epoch-zero.
683
688
  *
684
- * @param hosts - The TinyCloud hosts this session was created against,
685
- * as persisted in {@link PersistedSessionData.tinycloudHosts}. When
686
- * present (and non-empty) they are adopted directly so the restored
687
- * session resolves to the same node as the original sign-in without
688
- * re-running registry/fallback resolution. When absent (old session)
689
- * hosts are resolved lazily on the first host-needing call via
690
- * {@link ensureTinyCloudHosts}.
689
+ * @param hosts - Persisted host hints for genuine no-entry onboarding.
690
+ * Current signed registry membership always takes precedence.
691
691
  */
692
692
  setRestoredTinyCloudSession(session, hosts) {
693
693
  const address = (0, import_sdk_core5.canonicalizeAddress)(session.address);
@@ -749,89 +749,84 @@ var NodeUserAuthorization = class {
749
749
  signature: session.signature
750
750
  };
751
751
  }
752
- /**
753
- * Ensure `tinycloudHosts` are resolved before a host-needing call.
754
- *
755
- * Fresh sign-in resolves hosts up front; a restored session may not have
756
- * (old persisted sessions predate {@link PersistedSessionData.tinycloudHosts}).
757
- * This guard makes a restored session resolve the node exactly like a fresh
758
- * sign-in — persisted hosts are preferred (already set by
759
- * {@link setRestoredTinyCloudSession}), otherwise the registry/fallback
760
- * resolution runs lazily here. Idempotent: {@link resolveTinyCloudHostsForSignIn}
761
- * early-returns when hosts are already set.
762
- *
763
- * Throws if hosts are unset and the restored session has no address/chainId
764
- * to resolve from — a real failure that must surface, not be masked.
765
- */
752
+ /** Refresh the primary space's registry destinations, including after restore. */
766
753
  async ensureTinyCloudHosts() {
767
- if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
768
- return;
769
- }
770
754
  if (this._address === void 0 || this._chainId === void 0) {
771
- throw new Error(
772
- "Cannot resolve TinyCloud hosts: no address/chainId available. Sign in or restore a session first."
773
- );
755
+ throw new Error("Cannot resolve TinyCloud hosts: sign in or restore a session first.");
756
+ }
757
+ await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId, this._tinyCloudSession?.spaceId);
758
+ }
759
+ async resolveTinyCloudHostsForSignIn(address, chainId, restoredSpaceId) {
760
+ const spaceId = restoredSpaceId ?? this.resolveSignInCapabilities(address, chainId).spaceId;
761
+ const destinations = await this.resolveResourceDestinations(spaceId, "read");
762
+ for (const destination of destinations) await (0, import_sdk_services2.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
763
+ this.tinycloudHosts = destinations.map(({ url }) => url);
764
+ }
765
+ /** Current full-resource registry routing; saved hosts are only no-entry hints. */
766
+ async resolveResourceDestinations(resource, access, noEntryHosts = this.tinycloudHosts ?? []) {
767
+ const owner = resource.startsWith("urn:tinycloud:encryption:") ? (0, import_sdk_services2.parseNetworkId)(resource.split("/")[0]).ownerDid : (0, import_sdk_core5.parseSpaceUri)(resource)?.owner ?? (() => {
768
+ const key = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
769
+ return key ? `did:key:${key[1]}` : void 0;
770
+ })();
771
+ if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
772
+ const subject = owner.startsWith("did:pkh:") ? (0, import_sdk_core5.registryOwnerSubjectV1)(owner.slice(owner.lastIndexOf(":") + 1), this.registryChainId) : owner;
773
+ for (let attempt = 1; ; attempt++) {
774
+ try {
775
+ const resolved = await (0, import_sdk_core5.resolveRegistryResource)(subject, resource, access, {
776
+ registryUrl: this.tinycloudRegistryUrl,
777
+ registryChainId: this.registryChainId,
778
+ sequenceStore: this.registrySequenceStore,
779
+ cacheStore: this.registryCacheStore,
780
+ onRegistryFallback: this.onRegistryFallback
781
+ });
782
+ this.registryVerdict = resolved.verdict;
783
+ if (resolved.verdict.status === "unregistered") {
784
+ if (noEntryHosts.length === 0 && (this.autoDiscoverLocalNode ?? true)) {
785
+ const local = await (0, import_sdk_core5.discoverLocalTinyCloudNode)({
786
+ localNodeUrl: this.localNodeUrl,
787
+ localLinkName: this.localLinkName,
788
+ expectedNodeDid: this.expectedNodeDid,
789
+ identityStore: this.localNodeIdentityStore
790
+ });
791
+ if (local) {
792
+ (0, import_sdk_core5.warnRegistryDefaultNode)(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
793
+ return [{ url: local.url }];
794
+ }
795
+ }
796
+ const host = noEntryHosts[0] ?? this.newUserHost ?? (this.tinycloudFallbackHosts === void 0 ? "https://node.tinycloud.xyz" : this.tinycloudFallbackHosts?.[0]);
797
+ if (!host || this.newUserHost === null && noEntryHosts.length === 0) throw new import_sdk_core5.NodeNotFoundError(subject, resolved.verdict);
798
+ const url = (0, import_sdk_core5.exactHttpOrigin)(host, "new-user host");
799
+ (0, import_sdk_core5.warnRegistryDefaultNode)(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
800
+ return [{ url }];
801
+ }
802
+ const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
803
+ return destinations;
804
+ } catch (error) {
805
+ if (!(error instanceof import_sdk_core5.NodeNotFoundError) && !(error instanceof import_sdk_core5.RegistryUnavailableError)) throw error;
806
+ if (!this.onNodeNotFound) throw error;
807
+ const decision = await this.onNodeNotFound({ error, attempt });
808
+ if (decision.action === "retry") continue;
809
+ if (decision.action === "use-host" && error.verdict?.status === "unregistered") {
810
+ const url = (0, import_sdk_core5.exactHttpOrigin)(decision.host, "new-user host");
811
+ (0, import_sdk_core5.warnRegistryDefaultNode)(error.verdict, this.registryCacheStore, this.onRegistryFallback);
812
+ return [{ url }];
813
+ }
814
+ throw error;
815
+ }
774
816
  }
775
- await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId);
776
817
  }
777
- async resolveTinyCloudHostsForSignIn(address, chainId) {
778
- if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
779
- return;
780
- }
781
- if (this.registryPolicy === "fail-closed") {
782
- const resolved2 = await (0, import_sdk_core5.resolveFailClosedTinyCloudHosts)(address, {
783
- registryUrl: this.tinycloudRegistryUrl,
784
- registryChainId: this.registryChainId,
785
- newUserHost: this.newUserHost,
786
- sequenceStore: this.registrySequenceStore,
787
- onNodeNotFound: this.onNodeNotFound,
788
- autoDiscoverLocalNode: this.autoDiscoverLocalNode,
789
- localNodeUrl: this.localNodeUrl,
790
- localLinkName: this.localLinkName,
791
- expectedNodeDid: this.expectedNodeDid,
792
- localNodeIdentityStore: this.localNodeIdentityStore
793
- });
794
- this.tinycloudHosts = resolved2.hosts;
795
- this.registryVerdict = resolved2.verdict;
796
- return;
797
- }
798
- const subject = (0, import_sdk_core5.pkhDid)(address, chainId);
799
- const resolved = await (0, import_sdk_core5.resolveTinyCloudHosts)(subject, {
800
- registryUrl: this.tinycloudRegistryUrl,
801
- fallbackHosts: this.tinycloudFallbackHosts,
802
- autoDiscoverLocalNode: this.autoDiscoverLocalNode,
803
- localNodeUrl: this.localNodeUrl,
804
- localLinkName: this.localLinkName,
805
- expectedNodeDid: this.expectedNodeDid,
806
- localNodeIdentityStore: this.localNodeIdentityStore
807
- });
808
- this.tinycloudHosts = resolved.hosts;
818
+ get registryRecordPresent() {
819
+ return this.registryVerdict?.status === "registered";
809
820
  }
810
- /**
811
- * Space-creation guard (TC-1443). Under `registryPolicy: "fail-closed"`,
812
- * throws unless the owner's registry verdict allows a space on the primary
813
- * host: `NodeRegistryEntryExistsError` when the owner's record does not
814
- * list it, `RegistryUnavailableError` when the registry gives no
815
- * verdict. Reuses this session's sign-in verdict unless it was
816
- * `unavailable`, else looks it up now (explicit-host apps). Under the
817
- * default policy it returns at once and performs no lookup.
818
- */
819
- async assertMayCreateSpace() {
820
- if (this.registryPolicy !== "fail-closed") return;
821
- if (this._address === void 0) {
822
- throw new Error("Must be signed in to create a space");
823
- }
824
- await this.ensureTinyCloudHosts();
825
- const subject = (0, import_sdk_core5.registryOwnerSubjectV1)(this._address, this.registryChainId);
826
- let verdict = this.registryVerdict;
827
- if (verdict?.subject !== subject || verdict.status === "unavailable") {
828
- verdict = await (0, import_sdk_core5.lookupOwnerRegistryVerdict)(subject, {
829
- registryUrl: this.tinycloudRegistryUrl,
830
- sequenceStore: this.registrySequenceStore
831
- });
832
- this.registryVerdict = verdict;
821
+ /** Refresh membership before hosting; a saved no-entry verdict never authorizes a later creation. */
822
+ async assertMayCreateSpace(spaceId = this._tinyCloudSession?.spaceId, host = this.primaryTinyCloudHost) {
823
+ if (!spaceId) throw new Error("Must be signed in to create a space");
824
+ const destinations = await this.resolveResourceDestinations(spaceId, "host");
825
+ if (!destinations.some((destination) => destination.url === host)) {
826
+ const subject = (0, import_sdk_core5.parseSpaceUri)(spaceId)?.owner;
827
+ if (!subject || !this.registryVerdict) throw new Error("Cannot resolve space owner");
828
+ (0, import_sdk_core5.assertSpaceCreationAllowed)(this.registryVerdict, host, spaceId);
833
829
  }
834
- (0, import_sdk_core5.assertSpaceCreationAllowed)(verdict, this.primaryTinyCloudHost);
835
830
  }
836
831
  requireTinyCloudHosts() {
837
832
  if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
@@ -1277,6 +1272,31 @@ var NodeUserAuthorization = class {
1277
1272
  checkCurrent();
1278
1273
  const host = context?.host ?? this.primaryTinyCloudHost;
1279
1274
  const spaceId = context?.spaceId ?? (typeof targetSpaceId === "string" ? targetSpaceId : this._tinyCloudSession.spaceId);
1275
+ const destinations = await this.resolveResourceDestinations(spaceId, "host");
1276
+ if (this.registryVerdict?.status === "registered") {
1277
+ const results = [];
1278
+ for (const destination of destinations) {
1279
+ checkCurrent();
1280
+ try {
1281
+ await (0, import_sdk_services2.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
1282
+ const result = await (0, import_sdk_core5.activateSessionWithHost)(destination.url, this._tinyCloudSession.delegationHeader, signal);
1283
+ results.push(result.skipped?.includes(spaceId) ? { ...result, success: false, status: 403, error: "The session does not activate the requested space" } : result);
1284
+ } catch (error) {
1285
+ if ((0, import_sdk_services2.routingFailure)(error) || signal?.aborted) throw error;
1286
+ throw new import_sdk_services2.NodeNotRespondingError([destination.url], error);
1287
+ }
1288
+ }
1289
+ const failed = results.flatMap((result, index) => result.success ? [] : [{ url: destinations[index].url, status: result.status }]);
1290
+ if (failed.length) {
1291
+ if (destinations.length === 1) return results[0];
1292
+ throw new import_sdk_services2.PartialNodeWriteError(destinations.filter((_, index) => results[index].success).map(({ url }) => url), failed);
1293
+ }
1294
+ return {
1295
+ ...results[0],
1296
+ activated: [...new Set(results.flatMap((result) => result.activated ?? []))],
1297
+ skipped: [...new Set(results.flatMap((result) => result.skipped ?? []))]
1298
+ };
1299
+ }
1280
1300
  const peerId = await (0, import_sdk_core5.fetchPeerId)(host, spaceId, signal);
1281
1301
  checkCurrent();
1282
1302
  const siwe = this.wasm.generateHostSIWEMessage(
@@ -1347,13 +1367,45 @@ var NodeUserAuthorization = class {
1347
1367
  if (!this._tinyCloudSession) {
1348
1368
  throw new Error("Must be signed in to ensure space exists");
1349
1369
  }
1350
- await this.ensureTinyCloudHosts();
1351
- const host = this.primaryTinyCloudHost;
1352
1370
  const primarySpaceId = this._tinyCloudSession.spaceId;
1353
- const result = await (0, import_sdk_core5.activateSessionWithHost)(
1354
- host,
1355
- this._tinyCloudSession.delegationHeader
1356
- );
1371
+ const primary = await this.resolveResourceDestinations(primarySpaceId, "read");
1372
+ this.tinycloudHosts = primary.map(({ url }) => url);
1373
+ const targets = new Map(primary.map((destination) => [destination.url, destination]));
1374
+ if (this.registryVerdict?.status === "registered" && this.registryVerdict.record) {
1375
+ const record = this.registryVerdict.record;
1376
+ const resources = Object.keys((0, import_sdk_core5.extractRecapAttenuations)(this._tinyCloudSession.siwe ?? ""));
1377
+ for (const space of record.spaces) {
1378
+ const registeredSpace = (0, import_sdk_core5.parseSpaceUri)(space.space);
1379
+ if (!resources.some((resource) => {
1380
+ const fullSpace = resource.split("/")[0];
1381
+ const requestedSpace = (0, import_sdk_core5.parseSpaceUri)(fullSpace);
1382
+ return fullSpace === space.space || registeredSpace !== null && requestedSpace !== null && registeredSpace.owner === requestedSpace.owner && registeredSpace.name === requestedSpace.name;
1383
+ })) continue;
1384
+ for (const membership of space.nodes) {
1385
+ const node = record.nodes.find((candidate) => candidate.did === membership.did);
1386
+ targets.set(node.url, { url: node.url, nodeDid: node.did });
1387
+ }
1388
+ }
1389
+ if (record.encryptionNode && resources.some((resource) => resource.startsWith("urn:tinycloud:encryption:"))) {
1390
+ const node = record.nodes.find((candidate) => candidate.did === record.encryptionNode);
1391
+ targets.set(node.url, { url: node.url, nodeDid: node.did });
1392
+ }
1393
+ }
1394
+ let result;
1395
+ for (const destination of targets.values()) {
1396
+ await (0, import_sdk_services2.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
1397
+ let activated;
1398
+ try {
1399
+ activated = await (0, import_sdk_core5.activateSessionWithHost)(destination.url, this._tinyCloudSession.delegationHeader);
1400
+ } catch (error) {
1401
+ throw new import_sdk_services2.NodeNotRespondingError([destination.url], error);
1402
+ }
1403
+ if (!activated.success && activated.status !== 404) {
1404
+ throw Object.assign(new Error(`Failed to activate session: ${describeHostFailure(activated)}`), { cause: activated });
1405
+ }
1406
+ if (destination.url === primary[0].url) result = activated;
1407
+ }
1408
+ if (!result) throw new Error("Primary space activation did not return a result");
1357
1409
  this.recordActivationSkippedSpaces(result, primarySpaceId);
1358
1410
  if (!result.success && result.status !== 404) {
1359
1411
  throw Object.assign(
@@ -1401,7 +1453,7 @@ var NodeUserAuthorization = class {
1401
1453
  /** {@link assertMayCreateSpace}, reporting a refusal to the creation handler before rethrowing it. */
1402
1454
  async guardSpaceCreation(handler, context) {
1403
1455
  try {
1404
- await this.assertMayCreateSpace();
1456
+ await this.assertMayCreateSpace(context.spaceId, context.host);
1405
1457
  } catch (error) {
1406
1458
  const failure = error instanceof Error ? error : new Error(String(error));
1407
1459
  handler.onSpaceCreationFailed?.(context, failure);
@@ -2545,6 +2597,7 @@ function rememberInvokeAny(invokeAny, sink, pending) {
2545
2597
 
2546
2598
  // src/delegation.ts
2547
2599
  var import_sdk_core6 = require("@tinycloud/sdk-core");
2600
+ var import_sdk_services4 = require("@tinycloud/sdk-services");
2548
2601
  var RuntimeDelegationRejectedError = class extends Error {
2549
2602
  constructor(reason, message) {
2550
2603
  super(message);
@@ -2571,7 +2624,8 @@ var SERVICE_BY_SHORT_NAME = {
2571
2624
  kv: "tinycloud.kv",
2572
2625
  sql: "tinycloud.sql",
2573
2626
  duckdb: "tinycloud.duckdb",
2574
- hooks: "tinycloud.hooks"
2627
+ hooks: "tinycloud.hooks",
2628
+ capabilities: "tinycloud.capabilities"
2575
2629
  };
2576
2630
  function authorizationWithoutBearer(authorization) {
2577
2631
  return authorization.replace(/^Bearer /i, "");
@@ -2702,8 +2756,8 @@ function signedAuthorityFromCompactUcan(authorization) {
2702
2756
  ));
2703
2757
  continue;
2704
2758
  }
2705
- const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/(kv|sql|duckdb|hooks)(?:\/|$)(.*)$/.exec(resource);
2706
- if (!match) {
2759
+ const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/([^/]+)(?:\/|$)(.*)$/.exec(resource);
2760
+ if (!match || !Object.prototype.hasOwnProperty.call(SERVICE_BY_SHORT_NAME, match[3])) {
2707
2761
  throw new Error(
2708
2762
  `Validated runtime delegation has an unsupported signed resource '${resource}'.`
2709
2763
  );
@@ -2848,13 +2902,8 @@ function resourcesMatch(actual, expected) {
2848
2902
  return JSON.stringify(actual.map(comparable)) === JSON.stringify(expected.map(comparable));
2849
2903
  }
2850
2904
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
2851
- const host = normalizedHost(options.host);
2852
- if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
2853
- reject(
2854
- "host_mismatch",
2855
- `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
2856
- );
2857
- }
2905
+ let host = normalizedHost(options.host);
2906
+ const differentHost = delegation.host !== void 0 && normalizedHost(delegation.host) !== host;
2858
2907
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
2859
2908
  reject("authority_invalid", "Runtime delegation has an invalid expiry.");
2860
2909
  }
@@ -2898,6 +2947,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
2898
2947
  if (options.authorize !== void 0 && !options.authorize(signed.permissions)) {
2899
2948
  throw new Error("Runtime delegation is not authorized for this runtime.");
2900
2949
  }
2950
+ if (differentHost) {
2951
+ const registeredHost = await node.resolveRegisteredDelegationHost?.(signed.permissions, host);
2952
+ if (registeredHost === void 0) {
2953
+ reject("host_mismatch", `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`);
2954
+ }
2955
+ host = normalizedHost(registeredHost);
2956
+ }
2901
2957
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
2902
2958
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
2903
2959
  reject(
@@ -2941,6 +2997,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
2941
2997
  await node.useRuntimeDelegation(installedCandidate);
2942
2998
  }
2943
2999
  } catch (error) {
3000
+ if ((0, import_sdk_services4.routingFailure)(error)) throw error;
2944
3001
  reject(
2945
3002
  activationRejectionReason(error),
2946
3003
  error instanceof Error ? error.message : String(error)
@@ -2959,6 +3016,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
2959
3016
  "Runtime delegation activation installed authority that differs from the validated authority."
2960
3017
  );
2961
3018
  }
3019
+ node.refreshRegistryForCapabilities?.(signed.permissions);
2962
3020
  return {
2963
3021
  cid,
2964
3022
  delegation: installed,
@@ -3022,10 +3080,11 @@ function deserializeDelegation(data) {
3022
3080
  }
3023
3081
 
3024
3082
  // src/TinyCloudNode.ts
3083
+ var import_sdk_services5 = require("@tinycloud/sdk-services");
3025
3084
  var import_siwe2 = require("siwe");
3026
3085
  var import_share_envelope = require("@tinycloud/share-envelope");
3027
3086
  var import_sdk_core11 = require("@tinycloud/sdk-core");
3028
- var import_sdk_services4 = require("@tinycloud/sdk-services");
3087
+ var import_sdk_services6 = require("@tinycloud/sdk-services");
3029
3088
  var import_requestLog2 = require("@tinycloud/sdk-services/telemetry/requestLog");
3030
3089
  var import_resource2 = require("@tinycloud/sdk-services/encryption/resource");
3031
3090
 
@@ -4001,6 +4060,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4001
4060
  * ```
4002
4061
  */
4003
4062
  constructor(config = {}) {
4063
+ this.registryRefreshOwners = /* @__PURE__ */ new Set();
4004
4064
  this.signer = null;
4005
4065
  this.auth = null;
4006
4066
  this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
@@ -4164,8 +4224,10 @@ var _TinyCloudNode = class _TinyCloudNode {
4164
4224
  this.config = {
4165
4225
  ...config,
4166
4226
  host: config.host ?? DEFAULT_HOST,
4227
+ registryPolicy: "fail-closed",
4167
4228
  // One store for every authorization this client creates.
4168
- registrySequenceStore: config.registrySequenceStore ?? (0, import_sdk_core11.createInMemoryRegistrySequenceStore)()
4229
+ registrySequenceStore: config.registrySequenceStore ?? (0, import_sdk_core11.createInMemoryRegistrySequenceStore)(),
4230
+ registryCacheStore: config.registryCacheStore ?? (0, import_sdk_core11.createInMemoryRegistryCacheStore)()
4169
4231
  };
4170
4232
  if (this.config.replication?.enabled) {
4171
4233
  const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime }) => {
@@ -4469,7 +4531,11 @@ var _TinyCloudNode = class _TinyCloudNode {
4469
4531
  // A session saved but never registered on this node is refused: it registers once.
4470
4532
  activate: (signal) => {
4471
4533
  assertCurrent();
4472
- return (0, import_sdk_core11.activateSessionWithHost)(host, session.delegationHeader, AbortSignal.any([signal, graph.signal]));
4534
+ const activationSignal = AbortSignal.any([signal, graph.signal]);
4535
+ if (primary && primary.delegationCid === session.delegationCid) {
4536
+ return this.activateSignedSession(primary, primary.siwe, host, activationSignal);
4537
+ }
4538
+ return this.activateResourceSession(session.delegationHeader, [spaceId], host, activationSignal);
4473
4539
  }
4474
4540
  });
4475
4541
  assertCurrent();
@@ -4605,14 +4671,38 @@ var _TinyCloudNode = class _TinyCloudNode {
4605
4671
  get requests() {
4606
4672
  return this._requestLog === void 0 ? void 0 : (0, import_requestLog2.readOnlyRequestLog)(this._requestLog);
4607
4673
  }
4608
- createServiceGraphLifetime() {
4674
+ throwRegistryRefusal(status, text) {
4675
+ if (status !== 403 && status !== 503 || !text.startsWith("registry_unavailable:") && !text.startsWith("registry_hosting_denied:")) return;
4676
+ const code = status === 503 ? "registry_unavailable" : "node_registry_entry_exists";
4677
+ throw Object.assign(new Error(status === 503 ? "The location registry is unavailable for this resource." : "The registry does not authorize this node for the requested resource."), { code, meta: { status } });
4678
+ }
4679
+ createServiceGraphLifetime(session) {
4680
+ const routedFetch = (0, import_sdk_services5.createResourceRoutingFetch)(
4681
+ async (url, init) => {
4682
+ try {
4683
+ const response = await globalThis.fetch(url, init);
4684
+ if (response.status === 403 || response.status === 503) {
4685
+ this.throwRegistryRefusal(response.status, await response.clone().text());
4686
+ }
4687
+ return response;
4688
+ } catch (error) {
4689
+ if ((0, import_sdk_services5.routingFailure)(error)) throw error;
4690
+ if (new URL(url).pathname.startsWith("/encryption/") && !init?.signal?.aborted) {
4691
+ throw new import_sdk_services5.NodeNotRespondingError([new URL(url).origin], error);
4692
+ }
4693
+ throw error;
4694
+ }
4695
+ },
4696
+ {
4697
+ resolveResource: (resource, access) => this.routingDestinations(resource, access),
4698
+ bindAudience: (authorization, nodeDid) => this.bindRoutedAudience(authorization, nodeDid, session),
4699
+ verifyDestination: (target) => (0, import_sdk_services5.verifyResourceDestination)(target, globalThis.fetch.bind(globalThis))
4700
+ }
4701
+ );
4609
4702
  return new ServiceGraphLifetime(
4610
4703
  this.invokeWithRuntimePermissions,
4611
4704
  this.invokeAnyWithRuntimePermissions,
4612
- // Resolve fetch at request time. This keeps the graph-owned lifetime
4613
- // signal while preserving the SDK's existing injectable global fetch
4614
- // behavior (including adapters that install it after construction).
4615
- (url, init) => globalThis.fetch(url, init),
4705
+ routedFetch,
4616
4706
  (context) => {
4617
4707
  this.storageFull.observe(context);
4618
4708
  if (this._requestLog) (0, import_requestLog2.observeReplicaReads)(context, this._requestLog);
@@ -4620,6 +4710,75 @@ var _TinyCloudNode = class _TinyCloudNode {
4620
4710
  this._requestLog
4621
4711
  );
4622
4712
  }
4713
+ async routingDestinations(resource, access, noEntryHosts) {
4714
+ const session = this.currentTinyCloudSession();
4715
+ if (session) this.refreshRegistryOwner((0, import_sdk_core11.pkhDid)(session.address, session.chainId));
4716
+ if (this.auth) return this.auth.resolveResourceDestinations(resource, access, noEntryHosts);
4717
+ const owner = resource.startsWith("urn:tinycloud:encryption:") ? (0, import_sdk_core11.parseNetworkId)(resource.split("/")[0]).ownerDid : (0, import_sdk_core11.parseSpaceUri)(resource)?.owner ?? (() => {
4718
+ const key = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
4719
+ return key ? `did:key:${key[1]}` : void 0;
4720
+ })();
4721
+ if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
4722
+ const subject = owner.startsWith("did:pkh:") ? (0, import_sdk_core11.pkhDid)(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
4723
+ const resolved = await (0, import_sdk_core11.resolveRegistryResource)(subject, resource, access, {
4724
+ registryUrl: this.config.tinycloudRegistryUrl,
4725
+ registryChainId: this.config.registryChainId,
4726
+ sequenceStore: this.config.registrySequenceStore,
4727
+ cacheStore: this.config.registryCacheStore,
4728
+ onRegistryFallback: this.config.onRegistryFallback
4729
+ });
4730
+ if (resolved.verdict.status === "unregistered") {
4731
+ const hint = noEntryHosts === void 0 ? this.explicitHost ?? this.config.host : noEntryHosts[0];
4732
+ const host = hint ?? this.config.newUserHost ?? (this.config.tinycloudFallbackHosts === void 0 ? DEFAULT_HOST : this.config.tinycloudFallbackHosts?.[0]);
4733
+ if (!host || !hint && this.config.newUserHost === null) throw new import_sdk_core11.NodeNotFoundError(subject, resolved.verdict);
4734
+ const url = (0, import_sdk_core11.exactHttpOrigin)(host, "new-user host");
4735
+ (0, import_sdk_core11.warnRegistryDefaultNode)(resolved.verdict, this.config.registryCacheStore, this.config.onRegistryFallback);
4736
+ return [{ url }];
4737
+ }
4738
+ const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
4739
+ return destinations;
4740
+ }
4741
+ /** Transport hints cannot override verified full-resource registry routing. */
4742
+ async resolveRegisteredDelegationHost(permissions, noEntryHost) {
4743
+ const resources = /* @__PURE__ */ new Map();
4744
+ let hasData = false;
4745
+ for (const permission of permissions) {
4746
+ const encryption = this.isEncryptionNetworkOperation(this.invocationServiceName(permission.service), permission.path, permission.space);
4747
+ const resource = encryption ? permission.path.split("/")[0] : permission.space;
4748
+ if (!resource) throw new import_sdk_services5.ResourceNotRoutedError("delegation");
4749
+ resources.set(resource, encryption ? "encryption" : "host");
4750
+ hasData || (hasData = !encryption);
4751
+ }
4752
+ let dataHost;
4753
+ let encryptionHost;
4754
+ let unserved;
4755
+ for (const [resource, access] of resources) {
4756
+ try {
4757
+ const destinations = await this.routingDestinations(resource, access, [noEntryHost]);
4758
+ if (destinations.some(({ nodeDid }) => nodeDid === void 0)) return void 0;
4759
+ if (access === "host") dataHost ?? (dataHost = destinations[0]?.url);
4760
+ else encryptionHost ?? (encryptionHost = destinations[0]?.url);
4761
+ } catch (error) {
4762
+ if (!(error instanceof import_sdk_core11.NodeNotFoundError) || error.verdict.status !== "registered") throw error;
4763
+ unserved ?? (unserved = error);
4764
+ }
4765
+ }
4766
+ const host = hasData ? dataHost : encryptionHost;
4767
+ if (!host) throw unserved ?? new import_sdk_services5.ResourceNotRoutedError("delegation");
4768
+ return host;
4769
+ }
4770
+ async bindRoutedAudience(authorization, nodeDid, stagedSession) {
4771
+ const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(authorization.split(".")[1])));
4772
+ const issuer = typeof payload === "object" && payload !== null && "iss" in payload ? payload.iss : void 0;
4773
+ const session = [
4774
+ stagedSession,
4775
+ this._serviceContext?.session,
4776
+ this.auth?.tinyCloudSession,
4777
+ ...this.runtimePermissionGrants.map((grant) => grant.session)
4778
+ ].find((candidate) => candidate?.verificationMethod === issuer);
4779
+ if (!session) throw new Error("Invocation signing session is not active");
4780
+ return rewriteInvocationAudience(authorization, nodeDid, session.jwk);
4781
+ }
4623
4782
  syncResolvedHostFromAuth() {
4624
4783
  const host = this.auth?.hosts[0];
4625
4784
  if (host) {
@@ -4733,6 +4892,29 @@ var _TinyCloudNode = class _TinyCloudNode {
4733
4892
  const session = this.currentTinyCloudSession();
4734
4893
  return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
4735
4894
  }
4895
+ /** Start one background refresh for each owner derived from verified authority. */
4896
+ refreshRegistryForCapabilities(permissions) {
4897
+ for (const permission of permissions) {
4898
+ try {
4899
+ const owner = permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:") ? (0, import_sdk_core11.parseNetworkId)(permission.path.split("/")[0]).ownerDid : permission.space ? this.ownerDidFromSpaceId(permission.space) : void 0;
4900
+ if (owner) this.refreshRegistryOwner(owner);
4901
+ } catch {
4902
+ }
4903
+ }
4904
+ }
4905
+ refreshRegistryOwner(owner) {
4906
+ if (this.config.tinycloudRegistryUrl === null) return;
4907
+ const subject = owner.startsWith("did:pkh:") ? (0, import_sdk_core11.pkhDid)(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
4908
+ const key = (0, import_sdk_core11.registryOwnerKeyV2)(subject);
4909
+ if (this.registryRefreshOwners.has(key)) return;
4910
+ this.registryRefreshOwners.add(key);
4911
+ void (0, import_sdk_core11.refreshOwnerRegistry)(subject, {
4912
+ registryUrl: this.config.tinycloudRegistryUrl,
4913
+ registryChainId: this.config.registryChainId,
4914
+ sequenceStore: this.config.registrySequenceStore,
4915
+ cacheStore: this.config.registryCacheStore
4916
+ }).catch(() => void 0);
4917
+ }
4736
4918
  /** Normalize signed capabilities without collapsing nested encryption into raw authority. */
4737
4919
  projectSignedRecapCapabilities(siwe) {
4738
4920
  const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
@@ -5164,6 +5346,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5164
5346
  const primarySession = this.currentTinyCloudSession();
5165
5347
  if (primarySession) {
5166
5348
  this.registerPrimarySessionGrant(primarySession);
5349
+ this.refreshRegistryOwner((0, import_sdk_core11.pkhDid)(primarySession.address, primarySession.chainId));
5350
+ this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
5167
5351
  if (this.auth?.spaceHosting === "unknown") {
5168
5352
  await this.checkSpaceHosting();
5169
5353
  this.assertSessionEpoch(epoch, "Sign-in");
@@ -5386,8 +5570,8 @@ var _TinyCloudNode = class _TinyCloudNode {
5386
5570
  const primarySession = auth.tinyCloudSession;
5387
5571
  const defaultSpaceId = this.ownedSpaceId("default");
5388
5572
  const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === import_sdk_core11.BOOTSTRAP_SESSION_REQUESTS.default;
5389
- if (steps.some((step) => step.kind === "host")) {
5390
- await this.assertMayCreateSpace();
5573
+ for (const step of steps) {
5574
+ if (step.kind === "host") await this.assertMayCreateSpace(step.spaceId);
5391
5575
  }
5392
5576
  for (const step of steps) {
5393
5577
  if (step.kind !== "session") continue;
@@ -5436,10 +5620,20 @@ var _TinyCloudNode = class _TinyCloudNode {
5436
5620
  if (!session) {
5437
5621
  throw new Error(`Missing bootstrap session for ${step.space}`);
5438
5622
  }
5439
- const activated = await (0, import_sdk_core11.activateSessionWithHost)(host, session.delegationHeader, graph.signal).catch((error) => {
5440
- graph.assertActive();
5441
- throw error;
5442
- });
5623
+ const destinations = await this.routingDestinations(step.spaceId, "host");
5624
+ let activated;
5625
+ for (const destination of destinations) {
5626
+ try {
5627
+ await (0, import_sdk_services5.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
5628
+ const result = await (0, import_sdk_core11.activateSessionWithHost)(destination.url, session.delegationHeader, graph.signal);
5629
+ if (!result.success) throw Object.assign(new Error("Bootstrap activation failed"), { cause: result });
5630
+ activated = result;
5631
+ } catch (error) {
5632
+ graph.assertActive();
5633
+ throw error;
5634
+ }
5635
+ }
5636
+ if (!activated) throw new Error("Bootstrap space has no destination");
5443
5637
  graph.assertActive();
5444
5638
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
5445
5639
  throw Object.assign(
@@ -5769,7 +5963,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5769
5963
  if (!this.currentSessionCoversAccountRegistry()) {
5770
5964
  if (!_TinyCloudNode.accountRegistrySkipLogged) {
5771
5965
  _TinyCloudNode.accountRegistrySkipLogged = true;
5772
- import_sdk_services4.tinyCloudDebugLogger.log("registry.skip");
5966
+ import_sdk_services6.tinyCloudDebugLogger.log("registry.skip");
5773
5967
  }
5774
5968
  return;
5775
5969
  }
@@ -6022,6 +6216,13 @@ var _TinyCloudNode = class _TinyCloudNode {
6022
6216
  if (!session) {
6023
6217
  throw new Error("Owned space hosting requires an active session");
6024
6218
  }
6219
+ await this.assertMayCreateSpace(spaceId);
6220
+ if (this.auth.registryRecordPresent) {
6221
+ const hosted = await this.hostOwnedSpaceOnce(spaceId, signal);
6222
+ if (!hosted.success) throw Object.assign(new Error(`Failed to activate listed space ${spaceId}: ${describeHostFailure2(hosted)}`), { cause: hosted });
6223
+ if (this.auth.tinyCloudSession === session) this.confirmedHostedSpaceIds.add(spaceId);
6224
+ return;
6225
+ }
6025
6226
  if (this.confirmedHostedSpaceIds.has(spaceId) || this.hostedOwnedSpaceIds.has(spaceId)) {
6026
6227
  return;
6027
6228
  }
@@ -6040,7 +6241,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6040
6241
  { cause: activation }
6041
6242
  );
6042
6243
  }
6043
- await this.assertMayCreateSpace();
6244
+ await this.assertMayCreateSpace(spaceId);
6044
6245
  if (signal?.aborted) throw signal.reason;
6045
6246
  const created = await this.hostOwnedSpaceOnce(spaceId, signal);
6046
6247
  if (!created.success) {
@@ -6101,17 +6302,19 @@ var _TinyCloudNode = class _TinyCloudNode {
6101
6302
  { cause: hosted }
6102
6303
  );
6103
6304
  }
6104
- const activation = await (0, import_sdk_core11.activateSessionWithHost)(
6105
- host,
6106
- session.delegationHeader
6107
- );
6108
- if (!activation.success || activation.skipped?.includes(spaceId)) {
6109
- throw Object.assign(
6110
- new Error(
6111
- `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
6112
- ),
6113
- { cause: activation }
6305
+ if (!this.auth.registryRecordPresent) {
6306
+ const activation = await (0, import_sdk_core11.activateSessionWithHost)(
6307
+ host,
6308
+ session.delegationHeader
6114
6309
  );
6310
+ if (!activation.success || activation.skipped?.includes(spaceId)) {
6311
+ throw Object.assign(
6312
+ new Error(
6313
+ `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
6314
+ ),
6315
+ { cause: activation }
6316
+ );
6317
+ }
6115
6318
  }
6116
6319
  if (this.auth?.tinyCloudSession === session) this.hostedOwnedSpaceIds.add(spaceId);
6117
6320
  try {
@@ -6155,11 +6358,9 @@ var _TinyCloudNode = class _TinyCloudNode {
6155
6358
  * session's recap doesn't cover it and the registry has no record; the host
6156
6359
  * SIWE is idempotent server-side. Must be called after {@link signIn}.
6157
6360
  *
6158
- * Under `registryPolicy: "fail-closed"`, hosting is refused with
6159
- * `NodeRegistryEntryExistsError` when the owner's registry record does not
6160
- * list the host, or `RegistryUnavailableError` when the registry gives no
6161
- * verdict. {@link hostOwnedSpace} itself is an explicit action and is not
6162
- * guarded.
6361
+ * Current signed per-space membership is checked before both automatic and
6362
+ * explicit hosting. Registered replicas use the session grant; no-entry
6363
+ * onboarding retains the host-SIWE flow.
6163
6364
  *
6164
6365
  * @param name - The owned space name (e.g. `"secrets"`).
6165
6366
  * @returns The hosted owned-space URI.
@@ -6169,27 +6370,20 @@ var _TinyCloudNode = class _TinyCloudNode {
6169
6370
  throw new Error("Not signed in. Call signIn() first.");
6170
6371
  }
6171
6372
  const spaceId = this.ownedSpaceId(name);
6373
+ await this.assertMayCreateSpace(spaceId);
6172
6374
  if (this.hostedOwnedSpaceIds.has(spaceId) || await this.isOwnedSpaceRegistered(spaceId)) {
6173
6375
  return spaceId;
6174
6376
  }
6175
- await this.assertMayCreateSpace();
6176
6377
  return this.hostOwnedSpace(name);
6177
6378
  }
6178
- /**
6179
- * Space-creation guard (TC-1443) for the automatic hosting paths: sign-in's
6180
- * owned spaces, account bootstrap, {@link ensureOwnedSpaceHosted} and
6181
- * {@link ensurePublicSpace}. Only `registryPolicy: "fail-closed"` consults
6182
- * the registry ({@link NodeUserAuthorization.assertMayCreateSpace}); the
6183
- * default policy performs no lookup.
6184
- */
6185
- async assertMayCreateSpace() {
6186
- if (this.config.registryPolicy !== "fail-closed") return;
6187
- await this.auth.assertMayCreateSpace();
6379
+ /** Refresh full-space membership before any automatic or explicit hosting. */
6380
+ async assertMayCreateSpace(spaceId) {
6381
+ await this.routingDestinations(spaceId, "host");
6188
6382
  }
6189
6383
  /** Registry-policy settings shared by every authorization this client creates. */
6190
6384
  registryPolicyConfig() {
6191
- const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound } = this.config;
6192
- return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound };
6385
+ const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback } = this.config;
6386
+ return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback };
6193
6387
  }
6194
6388
  /**
6195
6389
  * Submit the host delegation for an owned space, sharing one in-flight
@@ -6395,12 +6589,8 @@ var _TinyCloudNode = class _TinyCloudNode {
6395
6589
  }
6396
6590
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
6397
6591
  }
6398
- const resolvedHost = await this.resolveRestoredHost(
6399
- sessionData.tinycloudHosts,
6400
- restoredAddress,
6401
- sessionData.chainId
6402
- );
6403
- const stagedHost = resolvedHost ?? this.config.host;
6592
+ const restoreHints = this.explicitHost ? [this.explicitHost] : sessionData.tinycloudHosts ?? [];
6593
+ const stagedHost = (0, import_sdk_core11.exactHttpOrigin)(restoreHints[0] ?? this.config.host, "restored host hint");
6404
6594
  let restoredNodeFeatures;
6405
6595
  let replicaProtocolMismatch = false;
6406
6596
  if (this.config.replication?.enabled) {
@@ -6517,7 +6707,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6517
6707
  this.sessionManager = stagedManager;
6518
6708
  this.sessionKeyId = stagedKeyId;
6519
6709
  this.sessionKeyJwk = stagedJwk;
6520
- if (resolvedHost) this.config.host = resolvedHost;
6710
+ this.config.host = stagedHost;
6521
6711
  if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
6522
6712
  this.storageFull.clear();
6523
6713
  this._requestLog?.clear();
@@ -6584,6 +6774,10 @@ var _TinyCloudNode = class _TinyCloudNode {
6584
6774
  if (attempt === this.latestSessionAttempt) await this.endSession();
6585
6775
  this.assertSessionEpoch(epoch, "Session restore");
6586
6776
  }
6777
+ if (this.config.replication?.enabled) {
6778
+ if (stagedAddress !== void 0) this.refreshRegistryOwner((0, import_sdk_core11.pkhDid)(stagedAddress, stagedChainId));
6779
+ this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
6780
+ }
6587
6781
  }
6588
6782
  stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
6589
6783
  if (!session?.siwe) return { cache: void 0, grants: [] };
@@ -6632,7 +6826,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6632
6826
  };
6633
6827
  }
6634
6828
  stageRestoredServiceGraph(input) {
6635
- const graph = this.createServiceGraphLifetime();
6829
+ const graph = this.createServiceGraphLifetime(input.serviceSession);
6636
6830
  const core = this.auth ? new import_sdk_core11.TinyCloud(this.auth, {
6637
6831
  invokeAny: graph.invokeAny,
6638
6832
  telemetry: this.config.telemetry,
@@ -6857,56 +7051,6 @@ var _TinyCloudNode = class _TinyCloudNode {
6857
7051
  spaceService.updateConfig({ sharingService });
6858
7052
  return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
6859
7053
  }
6860
- /**
6861
- * Resolve the host a restored session should target.
6862
- *
6863
- * Mirrors fresh sign-in host resolution but for the restore path:
6864
- * an explicit/pinned host always wins, then the hosts the session was
6865
- * persisted with, then a lazy registry/fallback resolution for sessions
6866
- * that predate the persisted `tinycloudHosts` field. Returns `undefined`
6867
- * only when there's nothing to resolve from (no explicit host, no
6868
- * persisted hosts, and no address/chainId) — in which case the existing
6869
- * `config.host` (default) is left in place.
6870
- *
6871
- * Resolution failures are surfaced, not swallowed: a genuinely broken
6872
- * registry lookup throws rather than silently falling back to a wrong host.
6873
- */
6874
- async resolveRestoredHost(persistedHosts, address, chainId) {
6875
- if (this.explicitHost) {
6876
- return this.explicitHost;
6877
- }
6878
- if (persistedHosts && persistedHosts.length > 0) {
6879
- return persistedHosts[0];
6880
- }
6881
- if (address === void 0 || chainId === void 0) {
6882
- return void 0;
6883
- }
6884
- if (this.config.registryPolicy === "fail-closed") {
6885
- const resolved2 = await (0, import_sdk_core11.resolveFailClosedTinyCloudHosts)(address, {
6886
- registryUrl: this.config.tinycloudRegistryUrl,
6887
- registryChainId: this.config.registryChainId,
6888
- newUserHost: this.config.newUserHost,
6889
- sequenceStore: this.config.registrySequenceStore,
6890
- onNodeNotFound: this.config.onNodeNotFound,
6891
- autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
6892
- localNodeUrl: this.config.localNodeUrl,
6893
- localLinkName: this.config.localLinkName,
6894
- expectedNodeDid: this.config.expectedNodeDid,
6895
- localNodeIdentityStore: this.config.localNodeIdentityStore
6896
- });
6897
- return resolved2.hosts[0];
6898
- }
6899
- const resolved = await (0, import_sdk_core11.resolveTinyCloudHosts)((0, import_sdk_core11.pkhDid)(address, chainId), {
6900
- registryUrl: this.config.tinycloudRegistryUrl,
6901
- fallbackHosts: this.config.tinycloudFallbackHosts,
6902
- autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
6903
- localNodeUrl: this.config.localNodeUrl,
6904
- localLinkName: this.config.localLinkName,
6905
- expectedNodeDid: this.config.expectedNodeDid,
6906
- localNodeIdentityStore: this.config.localNodeIdentityStore
6907
- });
6908
- return resolved.hosts[0];
6909
- }
6910
7054
  /**
6911
7055
  * Resolve the currently-active TinyCloudSession, preferring the auth
6912
7056
  * layer's value (wallet mode) and falling back to the node-level
@@ -7381,10 +7525,10 @@ var _TinyCloudNode = class _TinyCloudNode {
7381
7525
  * second time. Only a value recorded for this exact host qualifies; older
7382
7526
  * nodes that omit `nodeId` fall through to the fetch.
7383
7527
  */
7384
- async fetchNodeId() {
7385
- const cached = this.auth?.nodeIdForHost(this.config.host);
7528
+ async fetchNodeId(host = this.config.host) {
7529
+ const cached = this.auth?.nodeIdForHost(host);
7386
7530
  if (cached) return cached;
7387
- const response = await fetch(`${this.config.host}/info`);
7531
+ const response = await fetch(`${host}/info`);
7388
7532
  if (!response.ok) {
7389
7533
  throw await (0, import_sdk_core11.httpResponseError)(response, "Failed to fetch node info");
7390
7534
  }
@@ -7432,10 +7576,24 @@ var _TinyCloudNode = class _TinyCloudNode {
7432
7576
  )
7433
7577
  };
7434
7578
  }
7435
- async fetchEncryptionNetworkAt(host, networkId, fetchFn) {
7436
- const response = await fetchFn(
7437
- `${host}/encryption/networks/${encodeURIComponent(networkId)}`
7438
- );
7579
+ async encryptionDestination(networkId) {
7580
+ const destination = (await this.routingDestinations(networkId, "encryption"))[0];
7581
+ if (!destination) throw new Error("No encryption destination is registered");
7582
+ await (0, import_sdk_services5.verifyResourceDestination)(destination, globalThis.fetch.bind(globalThis));
7583
+ return { ...destination, nodeDid: destination.nodeDid ?? await this.fetchNodeId(destination.url) };
7584
+ }
7585
+ async fetchEncryptionNetworkAt(host, networkId, fetchFn, callerSignal) {
7586
+ let response;
7587
+ const timeout = AbortSignal.timeout(5e3);
7588
+ try {
7589
+ response = await fetchFn(`${host}/encryption/networks/${encodeURIComponent(networkId)}`, {
7590
+ signal: callerSignal ? AbortSignal.any([callerSignal, timeout]) : timeout,
7591
+ redirect: "error"
7592
+ });
7593
+ } catch (error) {
7594
+ if ((0, import_sdk_services5.routingFailure)(error) || callerSignal?.aborted || !timeout.aborted && error instanceof Error && error.name === "AbortError") throw error;
7595
+ throw new import_sdk_services5.NodeNotRespondingError([host], error);
7596
+ }
7439
7597
  if (response.status === 404) return null;
7440
7598
  if (!response.ok) {
7441
7599
  throw await (0, import_sdk_core11.httpResponseError)(response, `Failed to fetch encryption network ${networkId}`);
@@ -7445,18 +7603,18 @@ var _TinyCloudNode = class _TinyCloudNode {
7445
7603
  }
7446
7604
  createEncryptionService(binding) {
7447
7605
  const graph = binding?.graph ?? this._serviceGraph;
7448
- const host = binding?.host ?? this.config.host;
7449
7606
  const session = binding?.session ?? this.requireEncryptionSession();
7450
7607
  const did = binding?.did ?? this.did;
7451
7608
  const address = binding?.address ?? this._address;
7452
7609
  const chainId = binding?.chainId ?? this._chainId;
7453
7610
  const crypto2 = this.createEncryptionCrypto();
7454
- const descriptorCache = /* @__PURE__ */ new Map();
7455
7611
  const transport = {
7456
- postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
7612
+ postDecrypt: async ({ targetNode, networkId, authorization, canonicalBody, signal }) => {
7457
7613
  graph.assertActive();
7614
+ const destination = await this.encryptionDestination(networkId);
7615
+ if (destination.nodeDid !== targetNode) throw new import_sdk_services5.NodeIdentityMismatchError(destination.url, destination.nodeDid, targetNode);
7458
7616
  const response = await graph.fetch(
7459
- `${host}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
7617
+ `${destination.url}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
7460
7618
  {
7461
7619
  method: "POST",
7462
7620
  headers: {
@@ -7469,7 +7627,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7469
7627
  );
7470
7628
  graph.assertActive();
7471
7629
  if (!response.ok) {
7472
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services4.parsePermissionHint)({
7630
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services6.parsePermissionHint)({
7473
7631
  service: "tinycloud.encryption",
7474
7632
  path: networkId,
7475
7633
  actions: ["tinycloud.encryption/decrypt"]
@@ -7484,6 +7642,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7484
7642
  }
7485
7643
  };
7486
7644
  return new import_sdk_core11.EncryptionService({
7645
+ resolveTargetNode: async (networkId) => (await this.encryptionDestination(networkId)).nodeDid,
7487
7646
  crypto: crypto2,
7488
7647
  signer: {
7489
7648
  signDecryptInvocation: async (input) => {
@@ -7507,18 +7666,8 @@ var _TinyCloudNode = class _TinyCloudNode {
7507
7666
  node: {
7508
7667
  fetchByNetworkId: async (networkId) => {
7509
7668
  graph.assertActive();
7510
- let pending = descriptorCache.get(networkId);
7511
- if (!pending) {
7512
- pending = this.fetchEncryptionNetworkAt(host, networkId, graph.fetch).then((descriptor2) => {
7513
- if (descriptor2 === null) descriptorCache.delete(networkId);
7514
- return descriptor2;
7515
- }).catch((error) => {
7516
- descriptorCache.delete(networkId);
7517
- throw error;
7518
- });
7519
- descriptorCache.set(networkId, pending);
7520
- }
7521
- const descriptor = await pending;
7669
+ const destination = await this.encryptionDestination(networkId);
7670
+ const descriptor = await this.fetchEncryptionNetworkAt(destination.url, networkId, graph.fetch, graph.signal);
7522
7671
  graph.assertActive();
7523
7672
  return descriptor;
7524
7673
  }
@@ -7526,13 +7675,15 @@ var _TinyCloudNode = class _TinyCloudNode {
7526
7675
  wellKnown: {
7527
7676
  fetchWellKnown: async (principal, discoveryKey) => {
7528
7677
  graph.assertActive();
7678
+ const destination = await this.encryptionDestination(this.getDefaultEncryptionNetworkId());
7679
+ if (this.auth?.registryRecordPresent) return null;
7529
7680
  if (!address || !didPrincipalMatches2(principal, did)) {
7530
7681
  return null;
7531
7682
  }
7532
7683
  const publicSpaceId = (0, import_sdk_core11.makePublicSpaceId)(address, chainId);
7533
7684
  const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
7534
7685
  const response = await graph.fetch(
7535
- `${host}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
7686
+ `${destination.url}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
7536
7687
  { method: "GET" }
7537
7688
  );
7538
7689
  graph.assertActive();
@@ -7902,7 +8053,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7902
8053
  const signature = await this.signer.signMessage(prepared.siwe);
7903
8054
  assertOwnerGraphActive();
7904
8055
  const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature });
7905
- const activation = await (0, import_sdk_core11.activateSessionWithHost)(host, delegationSession.delegationHeader);
8056
+ const activation = await this.activateSignedSession(delegationSession, prepared.siwe, host);
7906
8057
  assertOwnerGraphActive();
7907
8058
  if (!activation.success) {
7908
8059
  throw Object.assign(
@@ -8221,15 +8372,16 @@ var _TinyCloudNode = class _TinyCloudNode {
8221
8372
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
8222
8373
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
8223
8374
  return this.fetchEncryptionNetworkAt(
8224
- this.config.host,
8375
+ (await this.encryptionDestination(networkId)).url,
8225
8376
  networkId,
8226
8377
  globalThis.fetch.bind(globalThis)
8227
8378
  );
8228
8379
  }
8229
8380
  async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
8230
- const targetNode = await this.fetchNodeId();
8231
8381
  const ownerDid = this.did;
8232
8382
  const networkId = this.getDefaultEncryptionNetworkId(name);
8383
+ const destination = await this.encryptionDestination(networkId);
8384
+ const targetNode = destination.nodeDid;
8233
8385
  const body = {
8234
8386
  name,
8235
8387
  ownerDid,
@@ -8252,7 +8404,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8252
8404
  action: NETWORK_CREATE_ACTION2,
8253
8405
  facts
8254
8406
  });
8255
- const response = await fetch(`${this.config.host}/encryption/networks`, {
8407
+ const response = await this._serviceGraph.fetch(`${destination.url}/encryption/networks`, {
8256
8408
  method: "POST",
8257
8409
  headers: {
8258
8410
  Authorization: signed.authorization,
@@ -8345,7 +8497,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8345
8497
  );
8346
8498
  if (result.status !== "ok") {
8347
8499
  if (result.status === "permission_required") {
8348
- const hint = (0, import_sdk_services4.parsePermissionHint)(result.hint);
8500
+ const hint = (0, import_sdk_services6.parsePermissionHint)(result.hint);
8349
8501
  if (hint === void 0) return { status: "read_failed" };
8350
8502
  return {
8351
8503
  status: "permission_required",
@@ -8592,6 +8744,97 @@ var _TinyCloudNode = class _TinyCloudNode {
8592
8744
  }
8593
8745
  return { recap: exactVerifiedRecap(verified), expiresAt };
8594
8746
  }
8747
+ async activateResourceSession(delegationHeader, resources, hostHint, signal) {
8748
+ const targets = /* @__PURE__ */ new Map();
8749
+ const routedSpaces = [];
8750
+ let unserved;
8751
+ let requestedResource;
8752
+ for (const resource of new Set(resources)) {
8753
+ requestedResource ?? (requestedResource = resource);
8754
+ signal?.throwIfAborted();
8755
+ try {
8756
+ const encryption = resource.startsWith("urn:tinycloud:encryption:");
8757
+ const destinations = await this.routingDestinations(
8758
+ resource,
8759
+ encryption ? "encryption" : "host",
8760
+ [hostHint ?? this.config.host]
8761
+ );
8762
+ if (!encryption && destinations.some(({ nodeDid }) => nodeDid !== void 0)) routedSpaces.push(resource);
8763
+ for (const destination of destinations) {
8764
+ let target = targets.get(destination.url);
8765
+ if (!target) {
8766
+ target = { destination, spaces: [] };
8767
+ targets.set(destination.url, target);
8768
+ }
8769
+ if (!encryption && destination.nodeDid !== void 0) target.spaces.push(resource);
8770
+ }
8771
+ } catch (error) {
8772
+ if (!(error instanceof import_sdk_core11.NodeNotFoundError)) throw error;
8773
+ unserved ?? (unserved = error);
8774
+ }
8775
+ }
8776
+ if (targets.size === 0) throw unserved ?? new import_sdk_services5.ResourceNotRoutedError(requestedResource ?? "session grant");
8777
+ const succeeded = [];
8778
+ const accepted = [];
8779
+ const failed = [];
8780
+ let refusal;
8781
+ for (const { destination: target, spaces } of targets.values()) {
8782
+ signal?.throwIfAborted();
8783
+ try {
8784
+ await (0, import_sdk_services5.verifyResourceDestination)(target, globalThis.fetch.bind(globalThis));
8785
+ const timeout = AbortSignal.timeout(5e3);
8786
+ const result = await (0, import_sdk_core11.activateSessionWithHost)(
8787
+ target.url,
8788
+ delegationHeader,
8789
+ signal ? AbortSignal.any([signal, timeout]) : timeout
8790
+ );
8791
+ this.throwRegistryRefusal(result.status, result.error ?? "");
8792
+ if (result.success) {
8793
+ const skipped = result.skipped?.find((space) => spaces.some((expected) => this.spaceIdsEqual(space, expected)));
8794
+ if (skipped) {
8795
+ failed.push({ url: target.url, status: result.status, cause: new import_sdk_services5.ResourceNotRoutedError(skipped) });
8796
+ continue;
8797
+ }
8798
+ succeeded.push(target.url);
8799
+ accepted.push(result);
8800
+ } else {
8801
+ refusal = result;
8802
+ failed.push({ url: target.url, status: result.status, cause: result });
8803
+ }
8804
+ } catch (error) {
8805
+ if (signal?.aborted) throw error;
8806
+ failed.push({ url: target.url, cause: (0, import_sdk_services5.routingFailure)(error) ? error : new import_sdk_services5.NodeNotRespondingError([target.url], error) });
8807
+ }
8808
+ }
8809
+ if (failed.length > 0) {
8810
+ if (targets.size > 1) throw new import_sdk_services5.PartialNodeWriteError(succeeded, failed);
8811
+ if (refusal) return refusal;
8812
+ throw failed[0].cause;
8813
+ }
8814
+ const activated = new Set(accepted.flatMap((result) => result.activated ?? []));
8815
+ return {
8816
+ ...accepted[0],
8817
+ activated: [...activated],
8818
+ skipped: [...new Set(accepted.flatMap((result) => result.skipped ?? []))].filter((space) => !activated.has(space) && !routedSpaces.some((expected) => this.spaceIdsEqual(space, expected)))
8819
+ };
8820
+ }
8821
+ activateRoutedDelegation(delegation, signal) {
8822
+ const resources = this.operationsFromDelegation(delegation).map(
8823
+ (operation) => operation.resource?.startsWith("urn:tinycloud:encryption:") ? operation.resource.split("/")[0] : operation.spaceId ?? delegation.spaceId
8824
+ );
8825
+ return this.activateResourceSession(delegation.delegationHeader, resources, delegation.host, signal);
8826
+ }
8827
+ activateSignedSession(session, siwe, hostHint, signal) {
8828
+ const resources = this.parseRecapWithCaveats(siwe).map(
8829
+ (entry) => this.isEncryptionNetworkOperation(this.invocationServiceName(entry.service), entry.path, entry.space) ? entry.path.split("/")[0] : entry.space.startsWith("tinycloud:") ? entry.space : session.spaceId
8830
+ );
8831
+ return this.activateResourceSession(
8832
+ session.delegationHeader,
8833
+ resources.length > 0 ? resources : [session.spaceId],
8834
+ hostHint,
8835
+ signal
8836
+ );
8837
+ }
8595
8838
  /**
8596
8839
  * Install a portable runtime permission delegation into this SDK instance so
8597
8840
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -8611,16 +8854,12 @@ var _TinyCloudNode = class _TinyCloudNode {
8611
8854
  `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
8612
8855
  );
8613
8856
  }
8614
- const targetHost = delegation.host ?? this.config.host;
8615
8857
  let activateResult;
8616
8858
  try {
8617
- activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
8618
- targetHost,
8619
- delegation.delegationHeader,
8620
- graph.signal
8621
- );
8859
+ activateResult = await this.activateRoutedDelegation(delegation, graph.signal);
8622
8860
  } catch (cause) {
8623
8861
  graph.assertActive();
8862
+ if ((0, import_sdk_services5.routingFailure)(cause)) throw cause;
8624
8863
  throw Object.assign(
8625
8864
  new Error("The TinyCloud host could not be reached to activate the delegation."),
8626
8865
  { code: "ACTIVATION_TRANSPORT_FAILED", cause }
@@ -8776,11 +9015,15 @@ var _TinyCloudNode = class _TinyCloudNode {
8776
9015
  ...prepared,
8777
9016
  signature
8778
9017
  });
8779
- const activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
8780
- this.config.host,
8781
- delegatedSession.delegationHeader,
8782
- graph.signal
8783
- ).catch((error) => {
9018
+ const delegation = this.runtimeDelegationFromSession(
9019
+ delegatedSession,
9020
+ delegatedEntries,
9021
+ spaceId,
9022
+ captured,
9023
+ expiresAt,
9024
+ { siwe: prepared.siwe, signature }
9025
+ );
9026
+ const activateResult = await this.activateRoutedDelegation(delegation, graph.signal).catch((error) => {
8784
9027
  graph.assertActive();
8785
9028
  throw error;
8786
9029
  });
@@ -8796,14 +9039,6 @@ var _TinyCloudNode = class _TinyCloudNode {
8796
9039
  { cause: activateResult }
8797
9040
  );
8798
9041
  }
8799
- const delegation = this.runtimeDelegationFromSession(
8800
- delegatedSession,
8801
- delegatedEntries,
8802
- spaceId,
8803
- captured,
8804
- expiresAt,
8805
- { siwe: prepared.siwe, signature }
8806
- );
8807
9042
  graph.assertActive();
8808
9043
  assertCapturedKey?.();
8809
9044
  this.runtimePermissionGrants.push({
@@ -8948,7 +9183,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8948
9183
  if (!publicSpaceId) {
8949
9184
  throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
8950
9185
  }
8951
- await this.assertMayCreateSpace();
9186
+ await this.assertMayCreateSpace(publicSpaceId);
8952
9187
  await this.auth.hostPublicSpace(publicSpaceId);
8953
9188
  const kvActions = [import_sdk_core11.KV.PUT, import_sdk_core11.KV.GET, import_sdk_core11.KV.DEL, import_sdk_core11.KV.LIST, import_sdk_core11.KV.METADATA];
8954
9189
  const abilities = { kv: { "": kvActions } };
@@ -8971,10 +9206,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8971
9206
  ...prepared,
8972
9207
  signature
8973
9208
  });
8974
- const activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
8975
- this.config.host,
8976
- delegationSession.delegationHeader
8977
- );
9209
+ const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
8978
9210
  if (!activateResult.success) {
8979
9211
  throw Object.assign(
8980
9212
  new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
@@ -9524,10 +9756,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9524
9756
  if (onPrepared) {
9525
9757
  await onPrepared(delegation);
9526
9758
  }
9527
- const activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
9528
- this.config.host,
9529
- delegationHeader
9530
- );
9759
+ const activateResult = await this.activateRoutedDelegation(delegation);
9531
9760
  assertDelegationActivated(activateResult, delegation, requireActivation);
9532
9761
  return delegation;
9533
9762
  }
@@ -9561,10 +9790,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9561
9790
  if (onPrepared) {
9562
9791
  await onPrepared(delegation);
9563
9792
  }
9564
- const activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
9565
- targetHost,
9566
- delegationHeader
9567
- );
9793
+ const activateResult = await this.activateRoutedDelegation(delegation);
9568
9794
  assertDelegationActivated(activateResult, delegation, requireActivation);
9569
9795
  return delegation;
9570
9796
  }
@@ -10216,10 +10442,7 @@ var _TinyCloudNode = class _TinyCloudNode {
10216
10442
  ...prepared,
10217
10443
  signature
10218
10444
  });
10219
- const activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
10220
- this.config.host,
10221
- delegationSession.delegationHeader
10222
- );
10445
+ const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
10223
10446
  if (!activateResult.success) {
10224
10447
  throw Object.assign(
10225
10448
  new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
@@ -10264,10 +10487,7 @@ var _TinyCloudNode = class _TinyCloudNode {
10264
10487
  ...publicPrepared,
10265
10488
  signature: publicSignature
10266
10489
  });
10267
- const publicActivateResult = await (0, import_sdk_core11.activateSessionWithHost)(
10268
- this.config.host,
10269
- publicSession.delegationHeader
10270
- );
10490
+ const publicActivateResult = await this.activateSignedSession(publicSession, publicPrepared.siwe);
10271
10491
  if (publicActivateResult.success) {
10272
10492
  result.publicDelegation = {
10273
10493
  cid: publicSession.delegationCid,
@@ -10373,11 +10593,7 @@ var _TinyCloudNode = class _TinyCloudNode {
10373
10593
  ...prepared,
10374
10594
  signature
10375
10595
  });
10376
- const activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
10377
- targetHost,
10378
- invokerSession.delegationHeader,
10379
- graph.signal
10380
- ).catch((error) => {
10596
+ const activateResult = await this.activateSignedSession(invokerSession, prepared.siwe, targetHost, graph.signal).catch((error) => {
10381
10597
  graph.assertActive();
10382
10598
  throw error;
10383
10599
  });
@@ -10504,10 +10720,7 @@ var _TinyCloudNode = class _TinyCloudNode {
10504
10720
  ...prepared,
10505
10721
  signature
10506
10722
  });
10507
- const activateResult = await (0, import_sdk_core11.activateSessionWithHost)(
10508
- targetHost,
10509
- subDelegationSession.delegationHeader
10510
- );
10723
+ const activateResult = await this.activateSignedSession(subDelegationSession, prepared.siwe, targetHost);
10511
10724
  if (!activateResult.success) {
10512
10725
  throw Object.assign(
10513
10726
  new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
@@ -10587,7 +10800,10 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
10587
10800
  KVService,
10588
10801
  ManifestValidationError,
10589
10802
  MemorySessionStorage,
10803
+ NodeIdentityMismatchError,
10804
+ NodeNotRespondingError,
10590
10805
  NodeUserAuthorization,
10806
+ PartialNodeWriteError,
10591
10807
  PermissionNotInManifestError,
10592
10808
  PrefixedKVService,
10593
10809
  ProtocolMismatchError,