@tinycloud/node-sdk 3.12.0-beta.1 → 3.12.0-beta.3

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 (36) hide show
  1. package/README.md +39 -0
  2. package/dist/{NodeUserAuthorization-BtoDI9in.d.cts → NodeUserAuthorization-B_TFUlua.d.cts} +19 -53
  3. package/dist/{NodeUserAuthorization-BtoDI9in.d.ts → NodeUserAuthorization-B_TFUlua.d.ts} +19 -53
  4. package/dist/{TinyCloudNode-DLrINr98.d.ts → TinyCloudNode-Brcu8QoX.d.cts} +33 -59
  5. package/dist/{TinyCloudNode-C24728N_.d.cts → TinyCloudNode-DBJUnsMN.d.ts} +33 -59
  6. package/dist/{authority-BQN2Z3st.d.ts → authority-BFc5lesl.d.ts} +1 -1
  7. package/dist/{authority-DDdheOL6.d.cts → authority-DQ6R1HEG.d.cts} +1 -1
  8. package/dist/core.cjs +555 -320
  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 +581 -324
  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 +572 -337
  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 +582 -325
  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.cjs +48 -6
  31. package/dist/space-hosting.cjs.map +1 -1
  32. package/dist/space-hosting.d.cts +23 -8
  33. package/dist/space-hosting.d.ts +23 -8
  34. package/dist/space-hosting.js +48 -6
  35. package/dist/space-hosting.js.map +1 -1
  36. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -17443,7 +17443,7 @@ import {
17443
17443
  canonicalizeRecapCaveats as canonicalizeRecapCaveats3,
17444
17444
  isCapabilitySubset as isCapabilitySubset3,
17445
17445
  principalDid,
17446
- parseSpaceUri as parseSpaceUri2
17446
+ parseSpaceUri as parseSpaceUri3
17447
17447
  } from "@tinycloud/sdk-core";
17448
17448
  import { parseUcanGrant as replicaParseUcanGrant } from "@tinycloud/replica";
17449
17449
  function reportReplicaDenial(result, reason, onEvent, context) {
@@ -17625,7 +17625,7 @@ function replicationScopeFromSignedSession(input) {
17625
17625
  for (const resource of Object.keys(grant.att)) {
17626
17626
  const space = resource.match(/^(?:tinycloud:\/\/)?(.+?)\/kv(?:\/|$)/)?.[1];
17627
17627
  if (space !== input.persistedSpace) continue;
17628
- const owner = parseSpaceUri2(space);
17628
+ const owner = parseSpaceUri3(space);
17629
17629
  if (owner?.owner && owner.address && owner.chainId) return { space, principal: owner.owner };
17630
17630
  }
17631
17631
  return void 0;
@@ -18151,6 +18151,7 @@ import {
18151
18151
  decodeBase64Url,
18152
18152
  principalDidEquals
18153
18153
  } from "@tinycloud/sdk-core";
18154
+ import { routingFailure } from "@tinycloud/sdk-services";
18154
18155
  var RUNTIME_DELEGATION_REJECTIONS = [
18155
18156
  "host_mismatch",
18156
18157
  "expired",
@@ -18192,7 +18193,8 @@ var SERVICE_BY_SHORT_NAME = {
18192
18193
  kv: "tinycloud.kv",
18193
18194
  sql: "tinycloud.sql",
18194
18195
  duckdb: "tinycloud.duckdb",
18195
- hooks: "tinycloud.hooks"
18196
+ hooks: "tinycloud.hooks",
18197
+ capabilities: "tinycloud.capabilities"
18196
18198
  };
18197
18199
  function authorizationWithoutBearer(authorization) {
18198
18200
  return authorization.replace(/^Bearer /i, "");
@@ -18323,8 +18325,8 @@ function signedAuthorityFromCompactUcan(authorization) {
18323
18325
  ));
18324
18326
  continue;
18325
18327
  }
18326
- const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/(kv|sql|duckdb|hooks)(?:\/|$)(.*)$/.exec(resource);
18327
- if (!match) {
18328
+ const match = /^(?:tinycloud:\/\/([^/]+)|(tinycloud:[^/]+))\/([^/]+)(?:\/|$)(.*)$/.exec(resource);
18329
+ if (!match || !Object.prototype.hasOwnProperty.call(SERVICE_BY_SHORT_NAME, match[3])) {
18328
18330
  throw new Error(
18329
18331
  `Validated runtime delegation has an unsupported signed resource '${resource}'.`
18330
18332
  );
@@ -18469,13 +18471,8 @@ function resourcesMatch(actual, expected) {
18469
18471
  return JSON.stringify(actual.map(comparable)) === JSON.stringify(expected.map(comparable));
18470
18472
  }
18471
18473
  async function activateValidatedRuntimeDelegation(node, delegation, options) {
18472
- const host = normalizedHost(options.host);
18473
- if (delegation.host !== void 0 && normalizedHost(delegation.host) !== host) {
18474
- reject(
18475
- "host_mismatch",
18476
- `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`
18477
- );
18478
- }
18474
+ let host = normalizedHost(options.host);
18475
+ const differentHost = delegation.host !== void 0 && normalizedHost(delegation.host) !== host;
18479
18476
  if (!(delegation.expiry instanceof Date) || Number.isNaN(delegation.expiry.getTime())) {
18480
18477
  reject("authority_invalid", "Runtime delegation has an invalid expiry.");
18481
18478
  }
@@ -18519,6 +18516,13 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
18519
18516
  if (options.authorize !== void 0 && !options.authorize(signed2.permissions)) {
18520
18517
  throw new Error("Runtime delegation is not authorized for this runtime.");
18521
18518
  }
18519
+ if (differentHost) {
18520
+ const registeredHost = await node.resolveRegisteredDelegationHost?.(signed2.permissions, host);
18521
+ if (registeredHost === void 0) {
18522
+ reject("host_mismatch", `Runtime delegation host '${delegation.host}' does not match expected host '${options.host}'.`);
18523
+ }
18524
+ host = normalizedHost(registeredHost);
18525
+ }
18522
18526
  const previouslyInstalled = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
18523
18527
  if (previouslyInstalled !== void 0 && previouslyInstalled.delegationHeader.Authorization !== authorization) {
18524
18528
  reject(
@@ -18562,6 +18566,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
18562
18566
  await node.useRuntimeDelegation(installedCandidate);
18563
18567
  }
18564
18568
  } catch (error) {
18569
+ if (routingFailure(error)) throw error;
18565
18570
  reject(
18566
18571
  activationRejectionReason(error),
18567
18572
  error instanceof Error ? error.message : String(error)
@@ -18580,6 +18585,7 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
18580
18585
  "Runtime delegation activation installed authority that differs from the validated authority."
18581
18586
  );
18582
18587
  }
18588
+ node.refreshRegistryForCapabilities?.(signed2.permissions);
18583
18589
  return {
18584
18590
  cid,
18585
18591
  delegation: installed,
@@ -18689,6 +18695,15 @@ function deserializeDelegation(data) {
18689
18695
  }
18690
18696
 
18691
18697
  // src/TinyCloudNode.ts
18698
+ import {
18699
+ createResourceRoutingFetch,
18700
+ verifyResourceDestination as verifyResourceDestination2,
18701
+ NodeNotRespondingError as NodeNotRespondingError2,
18702
+ NodeIdentityMismatchError,
18703
+ PartialNodeWriteError as PartialNodeWriteError2,
18704
+ ResourceNotRoutedError,
18705
+ routingFailure as routingFailure3
18706
+ } from "@tinycloud/sdk-services";
18692
18707
  import { generateNonce, SiweMessage as SiweMessage3 } from "siwe";
18693
18708
  import { didKeyFromEd25519PublicKey } from "@tinycloud/share-envelope";
18694
18709
 
@@ -18762,9 +18777,15 @@ import {
18762
18777
  verifyDidKeyEd25519Signature,
18763
18778
  canonicalizeAddress as canonicalizeAddress2,
18764
18779
  pkhDid as pkhDid2,
18765
- resolveTinyCloudHosts as resolveTinyCloudHosts2,
18766
- resolveFailClosedTinyCloudHosts as resolveFailClosedTinyCloudHosts2,
18780
+ parseSpaceUri as parseSpaceUri2,
18781
+ resolveRegistryResource as resolveRegistryResource2,
18782
+ warnRegistryDefaultNode as warnRegistryDefaultNode2,
18783
+ NodeNotFoundError as NodeNotFoundError2,
18784
+ exactHttpOrigin as exactHttpOrigin2,
18767
18785
  createInMemoryRegistrySequenceStore as createInMemoryRegistrySequenceStore2,
18786
+ createInMemoryRegistryCacheStore as createInMemoryRegistryCacheStore2,
18787
+ refreshOwnerRegistry,
18788
+ registryOwnerKeyV2,
18768
18789
  publishLocationRecord,
18769
18790
  principalDidEquals as principalDidEquals3,
18770
18791
  parseNetworkId as parseNetworkId2,
@@ -18811,11 +18832,16 @@ import {
18811
18832
  principalDidEquals as principalDidEquals2,
18812
18833
  resourceCapabilitiesToAbilitiesMap,
18813
18834
  resourceCapabilitiesToSpaceAbilitiesMap,
18814
- resolveTinyCloudHosts,
18815
- resolveFailClosedTinyCloudHosts,
18816
- lookupOwnerRegistryVerdict,
18835
+ resolveRegistryResource,
18836
+ warnRegistryDefaultNode,
18837
+ parseSpaceUri,
18838
+ NodeNotFoundError,
18839
+ RegistryUnavailableError,
18840
+ discoverLocalTinyCloudNode,
18841
+ exactHttpOrigin,
18817
18842
  assertSpaceCreationAllowed,
18818
18843
  createInMemoryRegistrySequenceStore,
18844
+ createInMemoryRegistryCacheStore,
18819
18845
  registryOwnerSubjectV1,
18820
18846
  EXPIRY,
18821
18847
  assertExpiryWithinTechnicalLimit,
@@ -18838,7 +18864,13 @@ import {
18838
18864
  ENCRYPTION
18839
18865
  } from "@tinycloud/sdk-core";
18840
18866
  import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
18841
- import { parseNetworkId } from "@tinycloud/sdk-services";
18867
+ import {
18868
+ parseNetworkId,
18869
+ verifyResourceDestination,
18870
+ NodeNotRespondingError,
18871
+ PartialNodeWriteError,
18872
+ routingFailure as routingFailure2
18873
+ } from "@tinycloud/sdk-services";
18842
18874
 
18843
18875
  // src/authorization/appKeyHostBinding.ts
18844
18876
  var DOMAIN_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -19116,11 +19148,12 @@ var NodeUserAuthorization = class {
19116
19148
  this.tinycloudHosts = config.tinycloudHosts;
19117
19149
  this.tinycloudRegistryUrl = config.tinycloudRegistryUrl;
19118
19150
  this.tinycloudFallbackHosts = config.tinycloudFallbackHosts;
19119
- this.registryPolicy = config.registryPolicy;
19120
19151
  this.newUserHost = config.newUserHost;
19121
19152
  this.registryChainId = config.registryChainId;
19122
19153
  this.registrySequenceStore = config.registrySequenceStore ?? createInMemoryRegistrySequenceStore();
19154
+ this.registryCacheStore = config.registryCacheStore ?? createInMemoryRegistryCacheStore();
19123
19155
  this.onNodeNotFound = config.onNodeNotFound;
19156
+ this.onRegistryFallback = config.onRegistryFallback;
19124
19157
  this.autoDiscoverLocalNode = config.autoDiscoverLocalNode;
19125
19158
  this.localNodeUrl = config.localNodeUrl;
19126
19159
  this.localLinkName = config.localLinkName;
@@ -19188,13 +19221,8 @@ var NodeUserAuthorization = class {
19188
19221
  * undefined for missing SIWEs and the SDK then treats the session as
19189
19222
  * expired-at-epoch-zero.
19190
19223
  *
19191
- * @param hosts - The TinyCloud hosts this session was created against,
19192
- * as persisted in {@link PersistedSessionData.tinycloudHosts}. When
19193
- * present (and non-empty) they are adopted directly so the restored
19194
- * session resolves to the same node as the original sign-in without
19195
- * re-running registry/fallback resolution. When absent (old session)
19196
- * hosts are resolved lazily on the first host-needing call via
19197
- * {@link ensureTinyCloudHosts}.
19224
+ * @param hosts - Persisted host hints for genuine no-entry onboarding.
19225
+ * Current signed registry membership always takes precedence.
19198
19226
  */
19199
19227
  setRestoredTinyCloudSession(session, hosts) {
19200
19228
  const address = canonicalizeAddress(session.address);
@@ -19256,89 +19284,84 @@ var NodeUserAuthorization = class {
19256
19284
  signature: session.signature
19257
19285
  };
19258
19286
  }
19259
- /**
19260
- * Ensure `tinycloudHosts` are resolved before a host-needing call.
19261
- *
19262
- * Fresh sign-in resolves hosts up front; a restored session may not have
19263
- * (old persisted sessions predate {@link PersistedSessionData.tinycloudHosts}).
19264
- * This guard makes a restored session resolve the node exactly like a fresh
19265
- * sign-in — persisted hosts are preferred (already set by
19266
- * {@link setRestoredTinyCloudSession}), otherwise the registry/fallback
19267
- * resolution runs lazily here. Idempotent: {@link resolveTinyCloudHostsForSignIn}
19268
- * early-returns when hosts are already set.
19269
- *
19270
- * Throws if hosts are unset and the restored session has no address/chainId
19271
- * to resolve from — a real failure that must surface, not be masked.
19272
- */
19287
+ /** Refresh the primary space's registry destinations, including after restore. */
19273
19288
  async ensureTinyCloudHosts() {
19274
- if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
19275
- return;
19276
- }
19277
19289
  if (this._address === void 0 || this._chainId === void 0) {
19278
- throw new Error(
19279
- "Cannot resolve TinyCloud hosts: no address/chainId available. Sign in or restore a session first."
19280
- );
19290
+ throw new Error("Cannot resolve TinyCloud hosts: sign in or restore a session first.");
19281
19291
  }
19282
- await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId);
19292
+ await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId, this._tinyCloudSession?.spaceId);
19283
19293
  }
19284
- async resolveTinyCloudHostsForSignIn(address, chainId) {
19285
- if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
19286
- return;
19287
- }
19288
- if (this.registryPolicy === "fail-closed") {
19289
- const resolved2 = await resolveFailClosedTinyCloudHosts(address, {
19290
- registryUrl: this.tinycloudRegistryUrl,
19291
- registryChainId: this.registryChainId,
19292
- newUserHost: this.newUserHost,
19293
- sequenceStore: this.registrySequenceStore,
19294
- onNodeNotFound: this.onNodeNotFound,
19295
- autoDiscoverLocalNode: this.autoDiscoverLocalNode,
19296
- localNodeUrl: this.localNodeUrl,
19297
- localLinkName: this.localLinkName,
19298
- expectedNodeDid: this.expectedNodeDid,
19299
- localNodeIdentityStore: this.localNodeIdentityStore
19300
- });
19301
- this.tinycloudHosts = resolved2.hosts;
19302
- this.registryVerdict = resolved2.verdict;
19303
- return;
19304
- }
19305
- const subject = pkhDid(address, chainId);
19306
- const resolved = await resolveTinyCloudHosts(subject, {
19307
- registryUrl: this.tinycloudRegistryUrl,
19308
- fallbackHosts: this.tinycloudFallbackHosts,
19309
- autoDiscoverLocalNode: this.autoDiscoverLocalNode,
19310
- localNodeUrl: this.localNodeUrl,
19311
- localLinkName: this.localLinkName,
19312
- expectedNodeDid: this.expectedNodeDid,
19313
- localNodeIdentityStore: this.localNodeIdentityStore
19314
- });
19315
- this.tinycloudHosts = resolved.hosts;
19294
+ async resolveTinyCloudHostsForSignIn(address, chainId, restoredSpaceId) {
19295
+ const spaceId = restoredSpaceId ?? this.resolveSignInCapabilities(address, chainId).spaceId;
19296
+ const destinations = await this.resolveResourceDestinations(spaceId, "read");
19297
+ for (const destination of destinations) await verifyResourceDestination(destination, globalThis.fetch.bind(globalThis));
19298
+ this.tinycloudHosts = destinations.map(({ url }) => url);
19316
19299
  }
19317
- /**
19318
- * Space-creation guard (TC-1443). Under `registryPolicy: "fail-closed"`,
19319
- * throws unless the owner's registry verdict allows a space on the primary
19320
- * host: `NodeRegistryEntryExistsError` when the owner's record does not
19321
- * list it, `RegistryUnavailableError` when the registry gives no
19322
- * verdict. Reuses this session's sign-in verdict unless it was
19323
- * `unavailable`, else looks it up now (explicit-host apps). Under the
19324
- * default policy it returns at once and performs no lookup.
19325
- */
19326
- async assertMayCreateSpace() {
19327
- if (this.registryPolicy !== "fail-closed") return;
19328
- if (this._address === void 0) {
19329
- throw new Error("Must be signed in to create a space");
19300
+ /** Current full-resource registry routing; saved hosts are only no-entry hints. */
19301
+ async resolveResourceDestinations(resource, access, noEntryHosts = this.tinycloudHosts ?? []) {
19302
+ const owner = resource.startsWith("urn:tinycloud:encryption:") ? parseNetworkId(resource.split("/")[0]).ownerDid : parseSpaceUri(resource)?.owner ?? (() => {
19303
+ const key2 = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
19304
+ return key2 ? `did:key:${key2[1]}` : void 0;
19305
+ })();
19306
+ if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
19307
+ const subject = owner.startsWith("did:pkh:") ? registryOwnerSubjectV1(owner.slice(owner.lastIndexOf(":") + 1), this.registryChainId) : owner;
19308
+ for (let attempt = 1; ; attempt++) {
19309
+ try {
19310
+ const resolved = await resolveRegistryResource(subject, resource, access, {
19311
+ registryUrl: this.tinycloudRegistryUrl,
19312
+ registryChainId: this.registryChainId,
19313
+ sequenceStore: this.registrySequenceStore,
19314
+ cacheStore: this.registryCacheStore,
19315
+ onRegistryFallback: this.onRegistryFallback
19316
+ });
19317
+ this.registryVerdict = resolved.verdict;
19318
+ if (resolved.verdict.status === "unregistered") {
19319
+ if (noEntryHosts.length === 0 && (this.autoDiscoverLocalNode ?? true)) {
19320
+ const local = await discoverLocalTinyCloudNode({
19321
+ localNodeUrl: this.localNodeUrl,
19322
+ localLinkName: this.localLinkName,
19323
+ expectedNodeDid: this.expectedNodeDid,
19324
+ identityStore: this.localNodeIdentityStore
19325
+ });
19326
+ if (local) {
19327
+ warnRegistryDefaultNode(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
19328
+ return [{ url: local.url }];
19329
+ }
19330
+ }
19331
+ const host = noEntryHosts[0] ?? this.newUserHost ?? (this.tinycloudFallbackHosts === void 0 ? "https://node.tinycloud.xyz" : this.tinycloudFallbackHosts?.[0]);
19332
+ if (!host || this.newUserHost === null && noEntryHosts.length === 0) throw new NodeNotFoundError(subject, resolved.verdict);
19333
+ const url = exactHttpOrigin(host, "new-user host");
19334
+ warnRegistryDefaultNode(resolved.verdict, this.registryCacheStore, this.onRegistryFallback);
19335
+ return [{ url }];
19336
+ }
19337
+ const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
19338
+ return destinations;
19339
+ } catch (error) {
19340
+ if (!(error instanceof NodeNotFoundError) && !(error instanceof RegistryUnavailableError)) throw error;
19341
+ if (!this.onNodeNotFound) throw error;
19342
+ const decision = await this.onNodeNotFound({ error, attempt });
19343
+ if (decision.action === "retry") continue;
19344
+ if (decision.action === "use-host" && error.verdict?.status === "unregistered") {
19345
+ const url = exactHttpOrigin(decision.host, "new-user host");
19346
+ warnRegistryDefaultNode(error.verdict, this.registryCacheStore, this.onRegistryFallback);
19347
+ return [{ url }];
19348
+ }
19349
+ throw error;
19350
+ }
19330
19351
  }
19331
- await this.ensureTinyCloudHosts();
19332
- const subject = registryOwnerSubjectV1(this._address, this.registryChainId);
19333
- let verdict = this.registryVerdict;
19334
- if (verdict?.subject !== subject || verdict.status === "unavailable") {
19335
- verdict = await lookupOwnerRegistryVerdict(subject, {
19336
- registryUrl: this.tinycloudRegistryUrl,
19337
- sequenceStore: this.registrySequenceStore
19338
- });
19339
- this.registryVerdict = verdict;
19352
+ }
19353
+ get registryRecordPresent() {
19354
+ return this.registryVerdict?.status === "registered";
19355
+ }
19356
+ /** Refresh membership before hosting; a saved no-entry verdict never authorizes a later creation. */
19357
+ async assertMayCreateSpace(spaceId = this._tinyCloudSession?.spaceId, host = this.primaryTinyCloudHost) {
19358
+ if (!spaceId) throw new Error("Must be signed in to create a space");
19359
+ const destinations = await this.resolveResourceDestinations(spaceId, "host");
19360
+ if (!destinations.some((destination) => destination.url === host)) {
19361
+ const subject = parseSpaceUri(spaceId)?.owner;
19362
+ if (!subject || !this.registryVerdict) throw new Error("Cannot resolve space owner");
19363
+ assertSpaceCreationAllowed(this.registryVerdict, host, spaceId);
19340
19364
  }
19341
- assertSpaceCreationAllowed(verdict, this.primaryTinyCloudHost);
19342
19365
  }
19343
19366
  requireTinyCloudHosts() {
19344
19367
  if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
@@ -19768,23 +19791,60 @@ var NodeUserAuthorization = class {
19768
19791
  * Create the space on the TinyCloud server (host delegation).
19769
19792
  * This registers the user as the owner of the space.
19770
19793
  */
19771
- async hostSpace(targetSpaceId, purpose, signal) {
19772
- if (!this._tinyCloudSession || !this._address || !this._chainId) {
19794
+ async hostSpace(targetSpaceId, purpose, signal, assertCurrent, siweParameters) {
19795
+ const context = typeof targetSpaceId === "object" ? targetSpaceId : void 0;
19796
+ const address = context?.address ?? this._address;
19797
+ const chainId = context?.chainId ?? this._chainId;
19798
+ if (!address || chainId === void 0 || !this._tinyCloudSession) {
19773
19799
  throw new Error("Must be signed in to host space");
19774
19800
  }
19801
+ const checkCurrent = () => {
19802
+ signal?.throwIfAborted();
19803
+ assertCurrent?.();
19804
+ };
19805
+ checkCurrent();
19775
19806
  await this.ensureTinyCloudHosts();
19776
- const host = this.primaryTinyCloudHost;
19777
- const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
19807
+ checkCurrent();
19808
+ const host = context?.host ?? this.primaryTinyCloudHost;
19809
+ const spaceId = context?.spaceId ?? (typeof targetSpaceId === "string" ? targetSpaceId : this._tinyCloudSession.spaceId);
19810
+ const destinations = await this.resolveResourceDestinations(spaceId, "host");
19811
+ if (this.registryVerdict?.status === "registered") {
19812
+ const results = [];
19813
+ for (const destination of destinations) {
19814
+ checkCurrent();
19815
+ try {
19816
+ await verifyResourceDestination(destination, globalThis.fetch.bind(globalThis));
19817
+ const result = await activateSessionWithHost(destination.url, this._tinyCloudSession.delegationHeader, signal);
19818
+ results.push(result.skipped?.includes(spaceId) ? { ...result, success: false, status: 403, error: "The session does not activate the requested space" } : result);
19819
+ } catch (error) {
19820
+ if (routingFailure2(error) || signal?.aborted) throw error;
19821
+ throw new NodeNotRespondingError([destination.url], error);
19822
+ }
19823
+ }
19824
+ const failed = results.flatMap((result, index) => result.success ? [] : [{ url: destinations[index].url, status: result.status }]);
19825
+ if (failed.length) {
19826
+ if (destinations.length === 1) return results[0];
19827
+ throw new PartialNodeWriteError(destinations.filter((_, index) => results[index].success).map(({ url }) => url), failed);
19828
+ }
19829
+ return {
19830
+ ...results[0],
19831
+ activated: [...new Set(results.flatMap((result) => result.activated ?? []))],
19832
+ skipped: [...new Set(results.flatMap((result) => result.skipped ?? []))]
19833
+ };
19834
+ }
19778
19835
  const peerId = await fetchPeerId(host, spaceId, signal);
19779
- const siwe = this.wasm.generateHostSIWEMessage({
19780
- address: this._address,
19781
- chainId: this._chainId,
19782
- domain: this.domain,
19783
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
19784
- spaceId,
19785
- peerId
19786
- });
19787
- if (signal?.aborted) throw signal.reason;
19836
+ checkCurrent();
19837
+ const siwe = this.wasm.generateHostSIWEMessage(
19838
+ context && siweParameters ? siweParameters(peerId) : {
19839
+ address,
19840
+ chainId,
19841
+ domain: this.domain,
19842
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
19843
+ spaceId,
19844
+ peerId
19845
+ }
19846
+ );
19847
+ checkCurrent();
19788
19848
  const signing = this.signMessage(siwe, purpose);
19789
19849
  let signature2;
19790
19850
  if (!signal) {
@@ -19803,8 +19863,9 @@ var NodeUserAuthorization = class {
19803
19863
  signal.removeEventListener("abort", onAbort);
19804
19864
  }
19805
19865
  }
19806
- if (signal?.aborted) throw signal.reason;
19866
+ checkCurrent();
19807
19867
  const headers = this.wasm.siweToDelegationHeaders({ siwe, signature: signature2 });
19868
+ checkCurrent();
19808
19869
  return submitHostDelegation(host, headers, signal);
19809
19870
  }
19810
19871
  /**
@@ -19841,13 +19902,45 @@ var NodeUserAuthorization = class {
19841
19902
  if (!this._tinyCloudSession) {
19842
19903
  throw new Error("Must be signed in to ensure space exists");
19843
19904
  }
19844
- await this.ensureTinyCloudHosts();
19845
- const host = this.primaryTinyCloudHost;
19846
19905
  const primarySpaceId = this._tinyCloudSession.spaceId;
19847
- const result = await activateSessionWithHost(
19848
- host,
19849
- this._tinyCloudSession.delegationHeader
19850
- );
19906
+ const primary = await this.resolveResourceDestinations(primarySpaceId, "read");
19907
+ this.tinycloudHosts = primary.map(({ url }) => url);
19908
+ const targets = new Map(primary.map((destination) => [destination.url, destination]));
19909
+ if (this.registryVerdict?.status === "registered" && this.registryVerdict.record) {
19910
+ const record = this.registryVerdict.record;
19911
+ const resources = Object.keys(extractRecapAttenuations(this._tinyCloudSession.siwe ?? ""));
19912
+ for (const space of record.spaces) {
19913
+ const registeredSpace = parseSpaceUri(space.space);
19914
+ if (!resources.some((resource) => {
19915
+ const fullSpace = resource.split("/")[0];
19916
+ const requestedSpace = parseSpaceUri(fullSpace);
19917
+ return fullSpace === space.space || registeredSpace !== null && requestedSpace !== null && registeredSpace.owner === requestedSpace.owner && registeredSpace.name === requestedSpace.name;
19918
+ })) continue;
19919
+ for (const membership of space.nodes) {
19920
+ const node = record.nodes.find((candidate) => candidate.did === membership.did);
19921
+ targets.set(node.url, { url: node.url, nodeDid: node.did });
19922
+ }
19923
+ }
19924
+ if (record.encryptionNode && resources.some((resource) => resource.startsWith("urn:tinycloud:encryption:"))) {
19925
+ const node = record.nodes.find((candidate) => candidate.did === record.encryptionNode);
19926
+ targets.set(node.url, { url: node.url, nodeDid: node.did });
19927
+ }
19928
+ }
19929
+ let result;
19930
+ for (const destination of targets.values()) {
19931
+ await verifyResourceDestination(destination, globalThis.fetch.bind(globalThis));
19932
+ let activated;
19933
+ try {
19934
+ activated = await activateSessionWithHost(destination.url, this._tinyCloudSession.delegationHeader);
19935
+ } catch (error) {
19936
+ throw new NodeNotRespondingError([destination.url], error);
19937
+ }
19938
+ if (!activated.success && activated.status !== 404) {
19939
+ throw Object.assign(new Error(`Failed to activate session: ${describeHostFailure(activated)}`), { cause: activated });
19940
+ }
19941
+ if (destination.url === primary[0].url) result = activated;
19942
+ }
19943
+ if (!result) throw new Error("Primary space activation did not return a result");
19851
19944
  this.recordActivationSkippedSpaces(result, primarySpaceId);
19852
19945
  if (!result.success && result.status !== 404) {
19853
19946
  throw Object.assign(
@@ -19866,43 +19959,36 @@ var NodeUserAuthorization = class {
19866
19959
  *
19867
19960
  * @throws Error if the creation handler fails or times out, or hosting fails
19868
19961
  */
19869
- async ensurePrimarySpaceHosted(hosting) {
19870
- if (hosting !== "not-hosted") {
19871
- return;
19872
- }
19962
+ async ensurePrimarySpaceHosted(hosting, signal) {
19873
19963
  const session = this._tinyCloudSession;
19874
- if (!session) {
19875
- throw new Error("Must be signed in to host the primary space");
19876
- }
19964
+ if (hosting !== "not-hosted") return;
19965
+ if (!session) throw new Error("Must be signed in to host the primary space");
19877
19966
  const handler = this.spaceCreationHandler ?? (this.autoCreateSpace ? new AutoApproveSpaceCreationHandler() : void 0);
19878
- if (!handler) {
19879
- return;
19880
- }
19881
- await this.ensureTinyCloudHosts();
19882
- const host = this.primaryTinyCloudHost;
19883
- const primarySpaceId = session.spaceId;
19884
- const creationContext = {
19885
- spaceId: primarySpaceId,
19886
- address: this._address,
19887
- chainId: this._chainId,
19888
- host
19889
- };
19967
+ if (!handler) return;
19890
19968
  const { createPrimarySpace } = await import("./space-hosting.js");
19891
19969
  await createPrimarySpace({
19892
19970
  handler,
19893
- context: creationContext,
19894
- guard: () => this.guardSpaceCreation(handler, creationContext),
19895
- host: () => this.hostSpace(primarySpaceId),
19971
+ session,
19972
+ currentSession: () => this._tinyCloudSession,
19973
+ signerAddress: () => this.signer.getAddress(),
19974
+ resolveHost: async () => {
19975
+ await this.ensureTinyCloudHosts();
19976
+ return this.primaryTinyCloudHost;
19977
+ },
19978
+ domain: this.domain,
19979
+ guard: (context) => this.guardSpaceCreation(handler, context),
19980
+ host: (context, assertCurrent, siweParameters) => this.hostSpace(context, void 0, signal, assertCurrent, siweParameters),
19896
19981
  onHosted: () => {
19897
19982
  this._spaceHosting = "hosted";
19898
19983
  },
19899
- activate: () => activateSessionWithHost(host, session.delegationHeader)
19984
+ activate: (context) => activateSessionWithHost(context.host, session.delegationHeader),
19985
+ signal
19900
19986
  });
19901
19987
  }
19902
19988
  /** {@link assertMayCreateSpace}, reporting a refusal to the creation handler before rethrowing it. */
19903
19989
  async guardSpaceCreation(handler, context) {
19904
19990
  try {
19905
- await this.assertMayCreateSpace();
19991
+ await this.assertMayCreateSpace(context.spaceId, context.host);
19906
19992
  } catch (error) {
19907
19993
  const failure = error instanceof Error ? error : new Error(String(error));
19908
19994
  handler.onSpaceCreationFailed?.(context, failure);
@@ -21824,6 +21910,7 @@ var _TinyCloudNode = class _TinyCloudNode {
21824
21910
  * ```
21825
21911
  */
21826
21912
  constructor(config = {}) {
21913
+ this.registryRefreshOwners = /* @__PURE__ */ new Set();
21827
21914
  this.signer = null;
21828
21915
  this.auth = null;
21829
21916
  this.consumedOpenKeyPendings = /* @__PURE__ */ new WeakSet();
@@ -21987,8 +22074,10 @@ var _TinyCloudNode = class _TinyCloudNode {
21987
22074
  this.config = {
21988
22075
  ...config,
21989
22076
  host: config.host ?? DEFAULT_HOST,
22077
+ registryPolicy: "fail-closed",
21990
22078
  // One store for every authorization this client creates.
21991
- registrySequenceStore: config.registrySequenceStore ?? createInMemoryRegistrySequenceStore2()
22079
+ registrySequenceStore: config.registrySequenceStore ?? createInMemoryRegistrySequenceStore2(),
22080
+ registryCacheStore: config.registryCacheStore ?? createInMemoryRegistryCacheStore2()
21992
22081
  };
21993
22082
  if (this.config.replication?.enabled) {
21994
22083
  const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime: ReplicationRuntime2 }) => {
@@ -22235,6 +22324,18 @@ var _TinyCloudNode = class _TinyCloudNode {
22235
22324
  return "unknown";
22236
22325
  }
22237
22326
  }
22327
+ /** Run the existing primary-space creation flow for the restored session. */
22328
+ async ensurePrimarySpaceHosted(hosting) {
22329
+ const graph = this._serviceGraph;
22330
+ const epoch = this.sessionEpoch;
22331
+ graph.assertActive();
22332
+ this.assertSessionEpoch(epoch, "Space hosting");
22333
+ const auth = this.auth;
22334
+ if (!auth || !this.session) return;
22335
+ await auth.ensurePrimarySpaceHosted(hosting, graph.signal);
22336
+ graph.assertActive();
22337
+ this.assertSessionEpoch(epoch, "Space hosting");
22338
+ }
22238
22339
  /**
22239
22340
  * The node's last answer for the primary space: recorded at sign-in from the
22240
22341
  * activation, or by {@link checkSpaceHosting}. `unknown` after a restore until
@@ -22280,7 +22381,11 @@ var _TinyCloudNode = class _TinyCloudNode {
22280
22381
  // A session saved but never registered on this node is refused: it registers once.
22281
22382
  activate: (signal) => {
22282
22383
  assertCurrent();
22283
- return activateSessionWithHost2(host, session.delegationHeader, AbortSignal.any([signal, graph.signal]));
22384
+ const activationSignal = AbortSignal.any([signal, graph.signal]);
22385
+ if (primary && primary.delegationCid === session.delegationCid) {
22386
+ return this.activateSignedSession(primary, primary.siwe, host, activationSignal);
22387
+ }
22388
+ return this.activateResourceSession(session.delegationHeader, [spaceId], host, activationSignal);
22284
22389
  }
22285
22390
  });
22286
22391
  assertCurrent();
@@ -22416,14 +22521,38 @@ var _TinyCloudNode = class _TinyCloudNode {
22416
22521
  get requests() {
22417
22522
  return this._requestLog === void 0 ? void 0 : readOnlyRequestLog(this._requestLog);
22418
22523
  }
22419
- createServiceGraphLifetime() {
22524
+ throwRegistryRefusal(status, text) {
22525
+ if (status !== 403 && status !== 503 || !text.startsWith("registry_unavailable:") && !text.startsWith("registry_hosting_denied:")) return;
22526
+ const code = status === 503 ? "registry_unavailable" : "node_registry_entry_exists";
22527
+ 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 } });
22528
+ }
22529
+ createServiceGraphLifetime(session) {
22530
+ const routedFetch = createResourceRoutingFetch(
22531
+ async (url, init2) => {
22532
+ try {
22533
+ const response = await globalThis.fetch(url, init2);
22534
+ if (response.status === 403 || response.status === 503) {
22535
+ this.throwRegistryRefusal(response.status, await response.clone().text());
22536
+ }
22537
+ return response;
22538
+ } catch (error) {
22539
+ if (routingFailure3(error)) throw error;
22540
+ if (new URL(url).pathname.startsWith("/encryption/") && !init2?.signal?.aborted) {
22541
+ throw new NodeNotRespondingError2([new URL(url).origin], error);
22542
+ }
22543
+ throw error;
22544
+ }
22545
+ },
22546
+ {
22547
+ resolveResource: (resource, access) => this.routingDestinations(resource, access),
22548
+ bindAudience: (authorization, nodeDid) => this.bindRoutedAudience(authorization, nodeDid, session),
22549
+ verifyDestination: (target) => verifyResourceDestination2(target, globalThis.fetch.bind(globalThis))
22550
+ }
22551
+ );
22420
22552
  return new ServiceGraphLifetime(
22421
22553
  this.invokeWithRuntimePermissions,
22422
22554
  this.invokeAnyWithRuntimePermissions,
22423
- // Resolve fetch at request time. This keeps the graph-owned lifetime
22424
- // signal while preserving the SDK's existing injectable global fetch
22425
- // behavior (including adapters that install it after construction).
22426
- (url, init2) => globalThis.fetch(url, init2),
22555
+ routedFetch,
22427
22556
  (context) => {
22428
22557
  this.storageFull.observe(context);
22429
22558
  if (this._requestLog) observeReplicaReads(context, this._requestLog);
@@ -22431,6 +22560,75 @@ var _TinyCloudNode = class _TinyCloudNode {
22431
22560
  this._requestLog
22432
22561
  );
22433
22562
  }
22563
+ async routingDestinations(resource, access, noEntryHosts) {
22564
+ const session = this.currentTinyCloudSession();
22565
+ if (session) this.refreshRegistryOwner(pkhDid2(session.address, session.chainId));
22566
+ if (this.auth) return this.auth.resolveResourceDestinations(resource, access, noEntryHosts);
22567
+ const owner = resource.startsWith("urn:tinycloud:encryption:") ? parseNetworkId2(resource.split("/")[0]).ownerDid : parseSpaceUri2(resource)?.owner ?? (() => {
22568
+ const key2 = /^tinycloud:key:(z[^:]+):[^/]+$/.exec(resource);
22569
+ return key2 ? `did:key:${key2[1]}` : void 0;
22570
+ })();
22571
+ if (!owner) throw new Error(`Cannot determine registry owner for ${resource}`);
22572
+ const subject = owner.startsWith("did:pkh:") ? pkhDid2(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
22573
+ const resolved = await resolveRegistryResource2(subject, resource, access, {
22574
+ registryUrl: this.config.tinycloudRegistryUrl,
22575
+ registryChainId: this.config.registryChainId,
22576
+ sequenceStore: this.config.registrySequenceStore,
22577
+ cacheStore: this.config.registryCacheStore,
22578
+ onRegistryFallback: this.config.onRegistryFallback
22579
+ });
22580
+ if (resolved.verdict.status === "unregistered") {
22581
+ const hint = noEntryHosts === void 0 ? this.explicitHost ?? this.config.host : noEntryHosts[0];
22582
+ const host = hint ?? this.config.newUserHost ?? (this.config.tinycloudFallbackHosts === void 0 ? DEFAULT_HOST : this.config.tinycloudFallbackHosts?.[0]);
22583
+ if (!host || !hint && this.config.newUserHost === null) throw new NodeNotFoundError2(subject, resolved.verdict);
22584
+ const url = exactHttpOrigin2(host, "new-user host");
22585
+ warnRegistryDefaultNode2(resolved.verdict, this.config.registryCacheStore, this.config.onRegistryFallback);
22586
+ return [{ url }];
22587
+ }
22588
+ const destinations = resolved.hosts.flatMap((host) => host.urls.map((url) => ({ url, nodeDid: host.nodeDid })));
22589
+ return destinations;
22590
+ }
22591
+ /** Transport hints cannot override verified full-resource registry routing. */
22592
+ async resolveRegisteredDelegationHost(permissions, noEntryHost) {
22593
+ const resources = /* @__PURE__ */ new Map();
22594
+ let hasData = false;
22595
+ for (const permission of permissions) {
22596
+ const encryption = this.isEncryptionNetworkOperation(this.invocationServiceName(permission.service), permission.path, permission.space);
22597
+ const resource = encryption ? permission.path.split("/")[0] : permission.space;
22598
+ if (!resource) throw new ResourceNotRoutedError("delegation");
22599
+ resources.set(resource, encryption ? "encryption" : "host");
22600
+ hasData || (hasData = !encryption);
22601
+ }
22602
+ let dataHost;
22603
+ let encryptionHost;
22604
+ let unserved;
22605
+ for (const [resource, access] of resources) {
22606
+ try {
22607
+ const destinations = await this.routingDestinations(resource, access, [noEntryHost]);
22608
+ if (destinations.some(({ nodeDid }) => nodeDid === void 0)) return void 0;
22609
+ if (access === "host") dataHost ?? (dataHost = destinations[0]?.url);
22610
+ else encryptionHost ?? (encryptionHost = destinations[0]?.url);
22611
+ } catch (error) {
22612
+ if (!(error instanceof NodeNotFoundError2) || error.verdict.status !== "registered") throw error;
22613
+ unserved ?? (unserved = error);
22614
+ }
22615
+ }
22616
+ const host = hasData ? dataHost : encryptionHost;
22617
+ if (!host) throw unserved ?? new ResourceNotRoutedError("delegation");
22618
+ return host;
22619
+ }
22620
+ async bindRoutedAudience(authorization, nodeDid, stagedSession) {
22621
+ const payload = JSON.parse(new TextDecoder().decode(base64UrlDecode(authorization.split(".")[1])));
22622
+ const issuer = typeof payload === "object" && payload !== null && "iss" in payload ? payload.iss : void 0;
22623
+ const session = [
22624
+ stagedSession,
22625
+ this._serviceContext?.session,
22626
+ this.auth?.tinyCloudSession,
22627
+ ...this.runtimePermissionGrants.map((grant) => grant.session)
22628
+ ].find((candidate) => candidate?.verificationMethod === issuer);
22629
+ if (!session) throw new Error("Invocation signing session is not active");
22630
+ return rewriteInvocationAudience(authorization, nodeDid, session.jwk);
22631
+ }
22434
22632
  syncResolvedHostFromAuth() {
22435
22633
  const host = this.auth?.hosts[0];
22436
22634
  if (host) {
@@ -22544,6 +22742,29 @@ var _TinyCloudNode = class _TinyCloudNode {
22544
22742
  const session = this.currentTinyCloudSession();
22545
22743
  return session?.siwe ? this.projectSignedRecapCapabilities(session.siwe) : [];
22546
22744
  }
22745
+ /** Start one background refresh for each owner derived from verified authority. */
22746
+ refreshRegistryForCapabilities(permissions) {
22747
+ for (const permission of permissions) {
22748
+ try {
22749
+ const owner = permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:") ? parseNetworkId2(permission.path.split("/")[0]).ownerDid : permission.space ? this.ownerDidFromSpaceId(permission.space) : void 0;
22750
+ if (owner) this.refreshRegistryOwner(owner);
22751
+ } catch {
22752
+ }
22753
+ }
22754
+ }
22755
+ refreshRegistryOwner(owner) {
22756
+ if (this.config.tinycloudRegistryUrl === null) return;
22757
+ const subject = owner.startsWith("did:pkh:") ? pkhDid2(owner.slice(owner.lastIndexOf(":") + 1), this.config.registryChainId ?? 1) : owner;
22758
+ const key2 = registryOwnerKeyV2(subject);
22759
+ if (this.registryRefreshOwners.has(key2)) return;
22760
+ this.registryRefreshOwners.add(key2);
22761
+ void refreshOwnerRegistry(subject, {
22762
+ registryUrl: this.config.tinycloudRegistryUrl,
22763
+ registryChainId: this.config.registryChainId,
22764
+ sequenceStore: this.config.registrySequenceStore,
22765
+ cacheStore: this.config.registryCacheStore
22766
+ }).catch(() => void 0);
22767
+ }
22547
22768
  /** Normalize signed capabilities without collapsing nested encryption into raw authority. */
22548
22769
  projectSignedRecapCapabilities(siwe) {
22549
22770
  const projected = this.parseRecapWithCaveats(siwe).map((entry) => {
@@ -22975,6 +23196,8 @@ var _TinyCloudNode = class _TinyCloudNode {
22975
23196
  const primarySession = this.currentTinyCloudSession();
22976
23197
  if (primarySession) {
22977
23198
  this.registerPrimarySessionGrant(primarySession);
23199
+ this.refreshRegistryOwner(pkhDid2(primarySession.address, primarySession.chainId));
23200
+ this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
22978
23201
  if (this.auth?.spaceHosting === "unknown") {
22979
23202
  await this.checkSpaceHosting();
22980
23203
  this.assertSessionEpoch(epoch, "Sign-in");
@@ -23197,8 +23420,8 @@ var _TinyCloudNode = class _TinyCloudNode {
23197
23420
  const primarySession = auth.tinyCloudSession;
23198
23421
  const defaultSpaceId = this.ownedSpaceId("default");
23199
23422
  const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === BOOTSTRAP_SESSION_REQUESTS.default;
23200
- if (steps.some((step) => step.kind === "host")) {
23201
- await this.assertMayCreateSpace();
23423
+ for (const step of steps) {
23424
+ if (step.kind === "host") await this.assertMayCreateSpace(step.spaceId);
23202
23425
  }
23203
23426
  for (const step of steps) {
23204
23427
  if (step.kind !== "session") continue;
@@ -23247,10 +23470,20 @@ var _TinyCloudNode = class _TinyCloudNode {
23247
23470
  if (!session) {
23248
23471
  throw new Error(`Missing bootstrap session for ${step.space}`);
23249
23472
  }
23250
- const activated = await activateSessionWithHost2(host, session.delegationHeader, graph.signal).catch((error) => {
23251
- graph.assertActive();
23252
- throw error;
23253
- });
23473
+ const destinations = await this.routingDestinations(step.spaceId, "host");
23474
+ let activated;
23475
+ for (const destination of destinations) {
23476
+ try {
23477
+ await verifyResourceDestination2(destination, globalThis.fetch.bind(globalThis));
23478
+ const result = await activateSessionWithHost2(destination.url, session.delegationHeader, graph.signal);
23479
+ if (!result.success) throw Object.assign(new Error("Bootstrap activation failed"), { cause: result });
23480
+ activated = result;
23481
+ } catch (error) {
23482
+ graph.assertActive();
23483
+ throw error;
23484
+ }
23485
+ }
23486
+ if (!activated) throw new Error("Bootstrap space has no destination");
23254
23487
  graph.assertActive();
23255
23488
  if (!activated.success || activated.skipped?.includes(step.spaceId)) {
23256
23489
  throw Object.assign(
@@ -23833,6 +24066,13 @@ var _TinyCloudNode = class _TinyCloudNode {
23833
24066
  if (!session) {
23834
24067
  throw new Error("Owned space hosting requires an active session");
23835
24068
  }
24069
+ await this.assertMayCreateSpace(spaceId);
24070
+ if (this.auth.registryRecordPresent) {
24071
+ const hosted = await this.hostOwnedSpaceOnce(spaceId, signal);
24072
+ if (!hosted.success) throw Object.assign(new Error(`Failed to activate listed space ${spaceId}: ${describeHostFailure2(hosted)}`), { cause: hosted });
24073
+ if (this.auth.tinyCloudSession === session) this.confirmedHostedSpaceIds.add(spaceId);
24074
+ return;
24075
+ }
23836
24076
  if (this.confirmedHostedSpaceIds.has(spaceId) || this.hostedOwnedSpaceIds.has(spaceId)) {
23837
24077
  return;
23838
24078
  }
@@ -23851,7 +24091,7 @@ var _TinyCloudNode = class _TinyCloudNode {
23851
24091
  { cause: activation }
23852
24092
  );
23853
24093
  }
23854
- await this.assertMayCreateSpace();
24094
+ await this.assertMayCreateSpace(spaceId);
23855
24095
  if (signal?.aborted) throw signal.reason;
23856
24096
  const created = await this.hostOwnedSpaceOnce(spaceId, signal);
23857
24097
  if (!created.success) {
@@ -23912,17 +24152,19 @@ var _TinyCloudNode = class _TinyCloudNode {
23912
24152
  { cause: hosted }
23913
24153
  );
23914
24154
  }
23915
- const activation = await activateSessionWithHost2(
23916
- host,
23917
- session.delegationHeader
23918
- );
23919
- if (!activation.success || activation.skipped?.includes(spaceId)) {
23920
- throw Object.assign(
23921
- new Error(
23922
- `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
23923
- ),
23924
- { cause: activation }
24155
+ if (!this.auth.registryRecordPresent) {
24156
+ const activation = await activateSessionWithHost2(
24157
+ host,
24158
+ session.delegationHeader
23925
24159
  );
24160
+ if (!activation.success || activation.skipped?.includes(spaceId)) {
24161
+ throw Object.assign(
24162
+ new Error(
24163
+ `Failed to activate session for owned space ${spaceId}: ${activation.success ? "space was skipped" : describeHostFailure2(activation)}`
24164
+ ),
24165
+ { cause: activation }
24166
+ );
24167
+ }
23926
24168
  }
23927
24169
  if (this.auth?.tinyCloudSession === session) this.hostedOwnedSpaceIds.add(spaceId);
23928
24170
  try {
@@ -23966,11 +24208,9 @@ var _TinyCloudNode = class _TinyCloudNode {
23966
24208
  * session's recap doesn't cover it and the registry has no record; the host
23967
24209
  * SIWE is idempotent server-side. Must be called after {@link signIn}.
23968
24210
  *
23969
- * Under `registryPolicy: "fail-closed"`, hosting is refused with
23970
- * `NodeRegistryEntryExistsError` when the owner's registry record does not
23971
- * list the host, or `RegistryUnavailableError` when the registry gives no
23972
- * verdict. {@link hostOwnedSpace} itself is an explicit action and is not
23973
- * guarded.
24211
+ * Current signed per-space membership is checked before both automatic and
24212
+ * explicit hosting. Registered replicas use the session grant; no-entry
24213
+ * onboarding retains the host-SIWE flow.
23974
24214
  *
23975
24215
  * @param name - The owned space name (e.g. `"secrets"`).
23976
24216
  * @returns The hosted owned-space URI.
@@ -23980,27 +24220,20 @@ var _TinyCloudNode = class _TinyCloudNode {
23980
24220
  throw new Error("Not signed in. Call signIn() first.");
23981
24221
  }
23982
24222
  const spaceId = this.ownedSpaceId(name);
24223
+ await this.assertMayCreateSpace(spaceId);
23983
24224
  if (this.hostedOwnedSpaceIds.has(spaceId) || await this.isOwnedSpaceRegistered(spaceId)) {
23984
24225
  return spaceId;
23985
24226
  }
23986
- await this.assertMayCreateSpace();
23987
24227
  return this.hostOwnedSpace(name);
23988
24228
  }
23989
- /**
23990
- * Space-creation guard (TC-1443) for the automatic hosting paths: sign-in's
23991
- * owned spaces, account bootstrap, {@link ensureOwnedSpaceHosted} and
23992
- * {@link ensurePublicSpace}. Only `registryPolicy: "fail-closed"` consults
23993
- * the registry ({@link NodeUserAuthorization.assertMayCreateSpace}); the
23994
- * default policy performs no lookup.
23995
- */
23996
- async assertMayCreateSpace() {
23997
- if (this.config.registryPolicy !== "fail-closed") return;
23998
- await this.auth.assertMayCreateSpace();
24229
+ /** Refresh full-space membership before any automatic or explicit hosting. */
24230
+ async assertMayCreateSpace(spaceId) {
24231
+ await this.routingDestinations(spaceId, "host");
23999
24232
  }
24000
24233
  /** Registry-policy settings shared by every authorization this client creates. */
24001
24234
  registryPolicyConfig() {
24002
- const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound } = this.config;
24003
- return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, onNodeNotFound };
24235
+ const { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback } = this.config;
24236
+ return { registryPolicy, newUserHost, registryChainId, registrySequenceStore, registryCacheStore, onNodeNotFound, onRegistryFallback };
24004
24237
  }
24005
24238
  /**
24006
24239
  * Submit the host delegation for an owned space, sharing one in-flight
@@ -24206,12 +24439,9 @@ var _TinyCloudNode = class _TinyCloudNode {
24206
24439
  }
24207
24440
  stagedSessionExpiry = signedExpiry ?? persistedPolicyExpiry ?? persistedExpiry(void 0);
24208
24441
  }
24209
- const resolvedHost = await this.resolveRestoredHost(
24210
- sessionData.tinycloudHosts,
24211
- restoredAddress,
24212
- sessionData.chainId
24213
- );
24214
- const stagedHost = resolvedHost ?? this.config.host;
24442
+ const restoreHints = this.explicitHost ? [this.explicitHost] : sessionData.tinycloudHosts ?? [];
24443
+ const resolvedHost = this.config.replication?.enabled ? (await this.routingDestinations(sessionData.spaceId, "read", restoreHints))[0]?.url : void 0;
24444
+ const stagedHost = resolvedHost ?? exactHttpOrigin2(restoreHints[0] ?? this.config.host, "restored host hint");
24215
24445
  let restoredNodeFeatures;
24216
24446
  let replicaProtocolMismatch = false;
24217
24447
  if (this.config.replication?.enabled) {
@@ -24328,7 +24558,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24328
24558
  this.sessionManager = stagedManager;
24329
24559
  this.sessionKeyId = stagedKeyId;
24330
24560
  this.sessionKeyJwk = stagedJwk;
24331
- if (resolvedHost) this.config.host = resolvedHost;
24561
+ this.config.host = stagedHost;
24332
24562
  if (spaceOwnerKey(serviceSession.spaceId) !== this.currentStorageOwner()) {
24333
24563
  this.storageFull.clear();
24334
24564
  this._requestLog?.clear();
@@ -24395,6 +24625,10 @@ var _TinyCloudNode = class _TinyCloudNode {
24395
24625
  if (attempt === this.latestSessionAttempt) await this.endSession();
24396
24626
  this.assertSessionEpoch(epoch, "Session restore");
24397
24627
  }
24628
+ if (this.config.replication?.enabled) {
24629
+ if (stagedAddress !== void 0) this.refreshRegistryOwner(pkhDid2(stagedAddress, stagedChainId));
24630
+ this.refreshRegistryForCapabilities(this.getVerifiedSessionCapabilities());
24631
+ }
24398
24632
  }
24399
24633
  stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
24400
24634
  if (!session?.siwe) return { cache: void 0, grants: [] };
@@ -24443,7 +24677,7 @@ var _TinyCloudNode = class _TinyCloudNode {
24443
24677
  };
24444
24678
  }
24445
24679
  stageRestoredServiceGraph(input) {
24446
- const graph = this.createServiceGraphLifetime();
24680
+ const graph = this.createServiceGraphLifetime(input.serviceSession);
24447
24681
  const core = this.auth ? new TinyCloud(this.auth, {
24448
24682
  invokeAny: graph.invokeAny,
24449
24683
  telemetry: this.config.telemetry,
@@ -24668,56 +24902,6 @@ var _TinyCloudNode = class _TinyCloudNode {
24668
24902
  spaceService.updateConfig({ sharingService });
24669
24903
  return { core, graph, serviceContext, kv, sql, duckdb, hooks, vault, encryption, capabilityRegistry, keyProvider, sharingService, delegationManager, spaceService };
24670
24904
  }
24671
- /**
24672
- * Resolve the host a restored session should target.
24673
- *
24674
- * Mirrors fresh sign-in host resolution but for the restore path:
24675
- * an explicit/pinned host always wins, then the hosts the session was
24676
- * persisted with, then a lazy registry/fallback resolution for sessions
24677
- * that predate the persisted `tinycloudHosts` field. Returns `undefined`
24678
- * only when there's nothing to resolve from (no explicit host, no
24679
- * persisted hosts, and no address/chainId) — in which case the existing
24680
- * `config.host` (default) is left in place.
24681
- *
24682
- * Resolution failures are surfaced, not swallowed: a genuinely broken
24683
- * registry lookup throws rather than silently falling back to a wrong host.
24684
- */
24685
- async resolveRestoredHost(persistedHosts, address, chainId) {
24686
- if (this.explicitHost) {
24687
- return this.explicitHost;
24688
- }
24689
- if (persistedHosts && persistedHosts.length > 0) {
24690
- return persistedHosts[0];
24691
- }
24692
- if (address === void 0 || chainId === void 0) {
24693
- return void 0;
24694
- }
24695
- if (this.config.registryPolicy === "fail-closed") {
24696
- const resolved2 = await resolveFailClosedTinyCloudHosts2(address, {
24697
- registryUrl: this.config.tinycloudRegistryUrl,
24698
- registryChainId: this.config.registryChainId,
24699
- newUserHost: this.config.newUserHost,
24700
- sequenceStore: this.config.registrySequenceStore,
24701
- onNodeNotFound: this.config.onNodeNotFound,
24702
- autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
24703
- localNodeUrl: this.config.localNodeUrl,
24704
- localLinkName: this.config.localLinkName,
24705
- expectedNodeDid: this.config.expectedNodeDid,
24706
- localNodeIdentityStore: this.config.localNodeIdentityStore
24707
- });
24708
- return resolved2.hosts[0];
24709
- }
24710
- const resolved = await resolveTinyCloudHosts2(pkhDid2(address, chainId), {
24711
- registryUrl: this.config.tinycloudRegistryUrl,
24712
- fallbackHosts: this.config.tinycloudFallbackHosts,
24713
- autoDiscoverLocalNode: this.config.autoDiscoverLocalNode,
24714
- localNodeUrl: this.config.localNodeUrl,
24715
- localLinkName: this.config.localLinkName,
24716
- expectedNodeDid: this.config.expectedNodeDid,
24717
- localNodeIdentityStore: this.config.localNodeIdentityStore
24718
- });
24719
- return resolved.hosts[0];
24720
- }
24721
24905
  /**
24722
24906
  * Resolve the currently-active TinyCloudSession, preferring the auth
24723
24907
  * layer's value (wallet mode) and falling back to the node-level
@@ -25192,10 +25376,10 @@ var _TinyCloudNode = class _TinyCloudNode {
25192
25376
  * second time. Only a value recorded for this exact host qualifies; older
25193
25377
  * nodes that omit `nodeId` fall through to the fetch.
25194
25378
  */
25195
- async fetchNodeId() {
25196
- const cached = this.auth?.nodeIdForHost(this.config.host);
25379
+ async fetchNodeId(host = this.config.host) {
25380
+ const cached = this.auth?.nodeIdForHost(host);
25197
25381
  if (cached) return cached;
25198
- const response = await fetch(`${this.config.host}/info`);
25382
+ const response = await fetch(`${host}/info`);
25199
25383
  if (!response.ok) {
25200
25384
  throw await httpResponseError(response, "Failed to fetch node info");
25201
25385
  }
@@ -25243,10 +25427,24 @@ var _TinyCloudNode = class _TinyCloudNode {
25243
25427
  )
25244
25428
  };
25245
25429
  }
25246
- async fetchEncryptionNetworkAt(host, networkId, fetchFn) {
25247
- const response = await fetchFn(
25248
- `${host}/encryption/networks/${encodeURIComponent(networkId)}`
25249
- );
25430
+ async encryptionDestination(networkId) {
25431
+ const destination = (await this.routingDestinations(networkId, "encryption"))[0];
25432
+ if (!destination) throw new Error("No encryption destination is registered");
25433
+ await verifyResourceDestination2(destination, globalThis.fetch.bind(globalThis));
25434
+ return { ...destination, nodeDid: destination.nodeDid ?? await this.fetchNodeId(destination.url) };
25435
+ }
25436
+ async fetchEncryptionNetworkAt(host, networkId, fetchFn, callerSignal) {
25437
+ let response;
25438
+ const timeout = AbortSignal.timeout(5e3);
25439
+ try {
25440
+ response = await fetchFn(`${host}/encryption/networks/${encodeURIComponent(networkId)}`, {
25441
+ signal: callerSignal ? AbortSignal.any([callerSignal, timeout]) : timeout,
25442
+ redirect: "error"
25443
+ });
25444
+ } catch (error) {
25445
+ if (routingFailure3(error) || callerSignal?.aborted || !timeout.aborted && error instanceof Error && error.name === "AbortError") throw error;
25446
+ throw new NodeNotRespondingError2([host], error);
25447
+ }
25250
25448
  if (response.status === 404) return null;
25251
25449
  if (!response.ok) {
25252
25450
  throw await httpResponseError(response, `Failed to fetch encryption network ${networkId}`);
@@ -25256,18 +25454,18 @@ var _TinyCloudNode = class _TinyCloudNode {
25256
25454
  }
25257
25455
  createEncryptionService(binding) {
25258
25456
  const graph = binding?.graph ?? this._serviceGraph;
25259
- const host = binding?.host ?? this.config.host;
25260
25457
  const session = binding?.session ?? this.requireEncryptionSession();
25261
25458
  const did = binding?.did ?? this.did;
25262
25459
  const address = binding?.address ?? this._address;
25263
25460
  const chainId = binding?.chainId ?? this._chainId;
25264
25461
  const crypto3 = this.createEncryptionCrypto();
25265
- const descriptorCache = /* @__PURE__ */ new Map();
25266
25462
  const transport = {
25267
- postDecrypt: async ({ networkId, authorization, canonicalBody, signal }) => {
25463
+ postDecrypt: async ({ targetNode, networkId, authorization, canonicalBody, signal }) => {
25268
25464
  graph.assertActive();
25465
+ const destination = await this.encryptionDestination(networkId);
25466
+ if (destination.nodeDid !== targetNode) throw new NodeIdentityMismatchError(destination.url, destination.nodeDid, targetNode);
25269
25467
  const response = await graph.fetch(
25270
- `${host}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
25468
+ `${destination.url}/encryption/networks/${encodeURIComponent(networkId)}/decrypt`,
25271
25469
  {
25272
25470
  method: "POST",
25273
25471
  headers: {
@@ -25295,6 +25493,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25295
25493
  }
25296
25494
  };
25297
25495
  return new EncryptionService({
25496
+ resolveTargetNode: async (networkId) => (await this.encryptionDestination(networkId)).nodeDid,
25298
25497
  crypto: crypto3,
25299
25498
  signer: {
25300
25499
  signDecryptInvocation: async (input) => {
@@ -25318,18 +25517,8 @@ var _TinyCloudNode = class _TinyCloudNode {
25318
25517
  node: {
25319
25518
  fetchByNetworkId: async (networkId) => {
25320
25519
  graph.assertActive();
25321
- let pending = descriptorCache.get(networkId);
25322
- if (!pending) {
25323
- pending = this.fetchEncryptionNetworkAt(host, networkId, graph.fetch).then((descriptor2) => {
25324
- if (descriptor2 === null) descriptorCache.delete(networkId);
25325
- return descriptor2;
25326
- }).catch((error) => {
25327
- descriptorCache.delete(networkId);
25328
- throw error;
25329
- });
25330
- descriptorCache.set(networkId, pending);
25331
- }
25332
- const descriptor = await pending;
25520
+ const destination = await this.encryptionDestination(networkId);
25521
+ const descriptor = await this.fetchEncryptionNetworkAt(destination.url, networkId, graph.fetch, graph.signal);
25333
25522
  graph.assertActive();
25334
25523
  return descriptor;
25335
25524
  }
@@ -25337,13 +25526,15 @@ var _TinyCloudNode = class _TinyCloudNode {
25337
25526
  wellKnown: {
25338
25527
  fetchWellKnown: async (principal, discoveryKey) => {
25339
25528
  graph.assertActive();
25529
+ const destination = await this.encryptionDestination(this.getDefaultEncryptionNetworkId());
25530
+ if (this.auth?.registryRecordPresent) return null;
25340
25531
  if (!address || !didPrincipalMatches2(principal, did)) {
25341
25532
  return null;
25342
25533
  }
25343
25534
  const publicSpaceId = makePublicSpaceId(address, chainId);
25344
25535
  const encodedKey = discoveryKey.split("/").map(encodeURIComponent).join("/");
25345
25536
  const response = await graph.fetch(
25346
- `${host}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
25537
+ `${destination.url}/public/${encodeURIComponent(publicSpaceId)}/kv/${encodedKey}`,
25347
25538
  { method: "GET" }
25348
25539
  );
25349
25540
  graph.assertActive();
@@ -25713,7 +25904,7 @@ var _TinyCloudNode = class _TinyCloudNode {
25713
25904
  const signature2 = await this.signer.signMessage(prepared.siwe);
25714
25905
  assertOwnerGraphActive();
25715
25906
  const delegationSession = this.wasmBindings.completeSessionSetup({ ...prepared, signature: signature2 });
25716
- const activation = await activateSessionWithHost2(host, delegationSession.delegationHeader);
25907
+ const activation = await this.activateSignedSession(delegationSession, prepared.siwe, host);
25717
25908
  assertOwnerGraphActive();
25718
25909
  if (!activation.success) {
25719
25910
  throw Object.assign(
@@ -26032,15 +26223,16 @@ var _TinyCloudNode = class _TinyCloudNode {
26032
26223
  async getEncryptionNetwork(nameOrNetworkId = this.getDefaultEncryptionNetworkId()) {
26033
26224
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
26034
26225
  return this.fetchEncryptionNetworkAt(
26035
- this.config.host,
26226
+ (await this.encryptionDestination(networkId)).url,
26036
26227
  networkId,
26037
26228
  globalThis.fetch.bind(globalThis)
26038
26229
  );
26039
26230
  }
26040
26231
  async createEncryptionNetwork(name = DEFAULT_ENCRYPTION_NETWORK_NAME) {
26041
- const targetNode = await this.fetchNodeId();
26042
26232
  const ownerDid = this.did;
26043
26233
  const networkId = this.getDefaultEncryptionNetworkId(name);
26234
+ const destination = await this.encryptionDestination(networkId);
26235
+ const targetNode = destination.nodeDid;
26044
26236
  const body = {
26045
26237
  name,
26046
26238
  ownerDid,
@@ -26063,7 +26255,7 @@ var _TinyCloudNode = class _TinyCloudNode {
26063
26255
  action: NETWORK_CREATE_ACTION2,
26064
26256
  facts
26065
26257
  });
26066
- const response = await fetch(`${this.config.host}/encryption/networks`, {
26258
+ const response = await this._serviceGraph.fetch(`${destination.url}/encryption/networks`, {
26067
26259
  method: "POST",
26068
26260
  headers: {
26069
26261
  Authorization: signed2.authorization,
@@ -26403,6 +26595,97 @@ var _TinyCloudNode = class _TinyCloudNode {
26403
26595
  }
26404
26596
  return { recap: exactVerifiedRecap(verified), expiresAt };
26405
26597
  }
26598
+ async activateResourceSession(delegationHeader, resources, hostHint, signal) {
26599
+ const targets = /* @__PURE__ */ new Map();
26600
+ const routedSpaces = [];
26601
+ let unserved;
26602
+ let requestedResource;
26603
+ for (const resource of new Set(resources)) {
26604
+ requestedResource ?? (requestedResource = resource);
26605
+ signal?.throwIfAborted();
26606
+ try {
26607
+ const encryption = resource.startsWith("urn:tinycloud:encryption:");
26608
+ const destinations = await this.routingDestinations(
26609
+ resource,
26610
+ encryption ? "encryption" : "host",
26611
+ [hostHint ?? this.config.host]
26612
+ );
26613
+ if (!encryption && destinations.some(({ nodeDid }) => nodeDid !== void 0)) routedSpaces.push(resource);
26614
+ for (const destination of destinations) {
26615
+ let target = targets.get(destination.url);
26616
+ if (!target) {
26617
+ target = { destination, spaces: [] };
26618
+ targets.set(destination.url, target);
26619
+ }
26620
+ if (!encryption && destination.nodeDid !== void 0) target.spaces.push(resource);
26621
+ }
26622
+ } catch (error) {
26623
+ if (!(error instanceof NodeNotFoundError2)) throw error;
26624
+ unserved ?? (unserved = error);
26625
+ }
26626
+ }
26627
+ if (targets.size === 0) throw unserved ?? new ResourceNotRoutedError(requestedResource ?? "session grant");
26628
+ const succeeded = [];
26629
+ const accepted = [];
26630
+ const failed = [];
26631
+ let refusal;
26632
+ for (const { destination: target, spaces } of targets.values()) {
26633
+ signal?.throwIfAborted();
26634
+ try {
26635
+ await verifyResourceDestination2(target, globalThis.fetch.bind(globalThis));
26636
+ const timeout = AbortSignal.timeout(5e3);
26637
+ const result = await activateSessionWithHost2(
26638
+ target.url,
26639
+ delegationHeader,
26640
+ signal ? AbortSignal.any([signal, timeout]) : timeout
26641
+ );
26642
+ this.throwRegistryRefusal(result.status, result.error ?? "");
26643
+ if (result.success) {
26644
+ const skipped = result.skipped?.find((space) => spaces.some((expected) => this.spaceIdsEqual(space, expected)));
26645
+ if (skipped) {
26646
+ failed.push({ url: target.url, status: result.status, cause: new ResourceNotRoutedError(skipped) });
26647
+ continue;
26648
+ }
26649
+ succeeded.push(target.url);
26650
+ accepted.push(result);
26651
+ } else {
26652
+ refusal = result;
26653
+ failed.push({ url: target.url, status: result.status, cause: result });
26654
+ }
26655
+ } catch (error) {
26656
+ if (signal?.aborted) throw error;
26657
+ failed.push({ url: target.url, cause: routingFailure3(error) ? error : new NodeNotRespondingError2([target.url], error) });
26658
+ }
26659
+ }
26660
+ if (failed.length > 0) {
26661
+ if (targets.size > 1) throw new PartialNodeWriteError2(succeeded, failed);
26662
+ if (refusal) return refusal;
26663
+ throw failed[0].cause;
26664
+ }
26665
+ const activated = new Set(accepted.flatMap((result) => result.activated ?? []));
26666
+ return {
26667
+ ...accepted[0],
26668
+ activated: [...activated],
26669
+ skipped: [...new Set(accepted.flatMap((result) => result.skipped ?? []))].filter((space) => !activated.has(space) && !routedSpaces.some((expected) => this.spaceIdsEqual(space, expected)))
26670
+ };
26671
+ }
26672
+ activateRoutedDelegation(delegation, signal) {
26673
+ const resources = this.operationsFromDelegation(delegation).map(
26674
+ (operation) => operation.resource?.startsWith("urn:tinycloud:encryption:") ? operation.resource.split("/")[0] : operation.spaceId ?? delegation.spaceId
26675
+ );
26676
+ return this.activateResourceSession(delegation.delegationHeader, resources, delegation.host, signal);
26677
+ }
26678
+ activateSignedSession(session, siwe, hostHint, signal) {
26679
+ const resources = this.parseRecapWithCaveats(siwe).map(
26680
+ (entry) => this.isEncryptionNetworkOperation(this.invocationServiceName(entry.service), entry.path, entry.space) ? entry.path.split("/")[0] : entry.space.startsWith("tinycloud:") ? entry.space : session.spaceId
26681
+ );
26682
+ return this.activateResourceSession(
26683
+ session.delegationHeader,
26684
+ resources.length > 0 ? resources : [session.spaceId],
26685
+ hostHint,
26686
+ signal
26687
+ );
26688
+ }
26406
26689
  /**
26407
26690
  * Install a portable runtime permission delegation into this SDK instance so
26408
26691
  * matching service calls and downstream `delegateTo()` calls can use it.
@@ -26422,16 +26705,12 @@ var _TinyCloudNode = class _TinyCloudNode {
26422
26705
  `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
26423
26706
  );
26424
26707
  }
26425
- const targetHost = delegation.host ?? this.config.host;
26426
26708
  let activateResult;
26427
26709
  try {
26428
- activateResult = await activateSessionWithHost2(
26429
- targetHost,
26430
- delegation.delegationHeader,
26431
- graph.signal
26432
- );
26710
+ activateResult = await this.activateRoutedDelegation(delegation, graph.signal);
26433
26711
  } catch (cause) {
26434
26712
  graph.assertActive();
26713
+ if (routingFailure3(cause)) throw cause;
26435
26714
  throw Object.assign(
26436
26715
  new Error("The TinyCloud host could not be reached to activate the delegation."),
26437
26716
  { code: "ACTIVATION_TRANSPORT_FAILED", cause }
@@ -26587,11 +26866,15 @@ var _TinyCloudNode = class _TinyCloudNode {
26587
26866
  ...prepared,
26588
26867
  signature: signature2
26589
26868
  });
26590
- const activateResult = await activateSessionWithHost2(
26591
- this.config.host,
26592
- delegatedSession.delegationHeader,
26593
- graph.signal
26594
- ).catch((error) => {
26869
+ const delegation = this.runtimeDelegationFromSession(
26870
+ delegatedSession,
26871
+ delegatedEntries,
26872
+ spaceId,
26873
+ captured,
26874
+ expiresAt,
26875
+ { siwe: prepared.siwe, signature: signature2 }
26876
+ );
26877
+ const activateResult = await this.activateRoutedDelegation(delegation, graph.signal).catch((error) => {
26595
26878
  graph.assertActive();
26596
26879
  throw error;
26597
26880
  });
@@ -26607,14 +26890,6 @@ var _TinyCloudNode = class _TinyCloudNode {
26607
26890
  { cause: activateResult }
26608
26891
  );
26609
26892
  }
26610
- const delegation = this.runtimeDelegationFromSession(
26611
- delegatedSession,
26612
- delegatedEntries,
26613
- spaceId,
26614
- captured,
26615
- expiresAt,
26616
- { siwe: prepared.siwe, signature: signature2 }
26617
- );
26618
26893
  graph.assertActive();
26619
26894
  assertCapturedKey?.();
26620
26895
  this.runtimePermissionGrants.push({
@@ -26759,7 +27034,7 @@ var _TinyCloudNode = class _TinyCloudNode {
26759
27034
  if (!publicSpaceId) {
26760
27035
  throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
26761
27036
  }
26762
- await this.assertMayCreateSpace();
27037
+ await this.assertMayCreateSpace(publicSpaceId);
26763
27038
  await this.auth.hostPublicSpace(publicSpaceId);
26764
27039
  const kvActions = [KV3.PUT, KV3.GET, KV3.DEL, KV3.LIST, KV3.METADATA];
26765
27040
  const abilities = { kv: { "": kvActions } };
@@ -26782,10 +27057,7 @@ var _TinyCloudNode = class _TinyCloudNode {
26782
27057
  ...prepared,
26783
27058
  signature: signature2
26784
27059
  });
26785
- const activateResult = await activateSessionWithHost2(
26786
- this.config.host,
26787
- delegationSession.delegationHeader
26788
- );
27060
+ const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
26789
27061
  if (!activateResult.success) {
26790
27062
  throw Object.assign(
26791
27063
  new Error(`Failed to activate public space delegation: ${describeHostFailure2(activateResult)}`),
@@ -27335,10 +27607,7 @@ var _TinyCloudNode = class _TinyCloudNode {
27335
27607
  if (onPrepared) {
27336
27608
  await onPrepared(delegation);
27337
27609
  }
27338
- const activateResult = await activateSessionWithHost2(
27339
- this.config.host,
27340
- delegationHeader
27341
- );
27610
+ const activateResult = await this.activateRoutedDelegation(delegation);
27342
27611
  assertDelegationActivated(activateResult, delegation, requireActivation);
27343
27612
  return delegation;
27344
27613
  }
@@ -27372,10 +27641,7 @@ var _TinyCloudNode = class _TinyCloudNode {
27372
27641
  if (onPrepared) {
27373
27642
  await onPrepared(delegation);
27374
27643
  }
27375
- const activateResult = await activateSessionWithHost2(
27376
- targetHost,
27377
- delegationHeader
27378
- );
27644
+ const activateResult = await this.activateRoutedDelegation(delegation);
27379
27645
  assertDelegationActivated(activateResult, delegation, requireActivation);
27380
27646
  return delegation;
27381
27647
  }
@@ -28027,10 +28293,7 @@ var _TinyCloudNode = class _TinyCloudNode {
28027
28293
  ...prepared,
28028
28294
  signature: signature2
28029
28295
  });
28030
- const activateResult = await activateSessionWithHost2(
28031
- this.config.host,
28032
- delegationSession.delegationHeader
28033
- );
28296
+ const activateResult = await this.activateSignedSession(delegationSession, prepared.siwe);
28034
28297
  if (!activateResult.success) {
28035
28298
  throw Object.assign(
28036
28299
  new Error(`Failed to activate delegation: ${describeHostFailure2(activateResult)}`),
@@ -28075,10 +28338,7 @@ var _TinyCloudNode = class _TinyCloudNode {
28075
28338
  ...publicPrepared,
28076
28339
  signature: publicSignature
28077
28340
  });
28078
- const publicActivateResult = await activateSessionWithHost2(
28079
- this.config.host,
28080
- publicSession.delegationHeader
28081
- );
28341
+ const publicActivateResult = await this.activateSignedSession(publicSession, publicPrepared.siwe);
28082
28342
  if (publicActivateResult.success) {
28083
28343
  result.publicDelegation = {
28084
28344
  cid: publicSession.delegationCid,
@@ -28184,11 +28444,7 @@ var _TinyCloudNode = class _TinyCloudNode {
28184
28444
  ...prepared,
28185
28445
  signature: signature2
28186
28446
  });
28187
- const activateResult = await activateSessionWithHost2(
28188
- targetHost,
28189
- invokerSession.delegationHeader,
28190
- graph.signal
28191
- ).catch((error) => {
28447
+ const activateResult = await this.activateSignedSession(invokerSession, prepared.siwe, targetHost, graph.signal).catch((error) => {
28192
28448
  graph.assertActive();
28193
28449
  throw error;
28194
28450
  });
@@ -28315,10 +28571,7 @@ var _TinyCloudNode = class _TinyCloudNode {
28315
28571
  ...prepared,
28316
28572
  signature: signature2
28317
28573
  });
28318
- const activateResult = await activateSessionWithHost2(
28319
- targetHost,
28320
- subDelegationSession.delegationHeader
28321
- );
28574
+ const activateResult = await this.activateSignedSession(subDelegationSession, prepared.siwe, targetHost);
28322
28575
  if (!activateResult.success) {
28323
28576
  throw Object.assign(
28324
28577
  new Error(`Failed to activate sub-delegation: ${describeHostFailure2(activateResult)}`),
@@ -28382,6 +28635,7 @@ registerReplicationLoaders({
28382
28635
 
28383
28636
  // src/index.ts
28384
28637
  import { TinyCloud as TinyCloud2 } from "@tinycloud/sdk-core";
28638
+ import { NodeNotRespondingError as NodeNotRespondingError3, NodeIdentityMismatchError as NodeIdentityMismatchError2, PartialNodeWriteError as PartialNodeWriteError3 } from "@tinycloud/sdk-services";
28385
28639
  import {
28386
28640
  SilentNotificationHandler as SilentNotificationHandler2,
28387
28641
  AutoApproveSpaceCreationHandler as AutoApproveSpaceCreationHandler2,
@@ -28699,7 +28953,7 @@ import {
28699
28953
  SpaceService as SpaceService2,
28700
28954
  SpaceErrorCodes,
28701
28955
  createSpaceService,
28702
- parseSpaceUri as parseSpaceUri3,
28956
+ parseSpaceUri as parseSpaceUri4,
28703
28957
  buildSpaceUri,
28704
28958
  makePublicSpaceId as makePublicSpaceId2,
28705
28959
  Space
@@ -31270,8 +31524,11 @@ export {
31270
31524
  MemorySessionStorage,
31271
31525
  NETWORK_NAME_PATTERN,
31272
31526
  NetworkIdError,
31527
+ NodeIdentityMismatchError2 as NodeIdentityMismatchError,
31528
+ NodeNotRespondingError3 as NodeNotRespondingError,
31273
31529
  NodeUserAuthorization,
31274
31530
  NodeWasmBindings,
31531
+ PartialNodeWriteError3 as PartialNodeWriteError,
31275
31532
  PermissionNotInManifestError2 as PermissionNotInManifestError,
31276
31533
  PrefixedKVService,
31277
31534
  PrivateKeySigner,
@@ -31379,7 +31636,7 @@ export {
31379
31636
  parseNetworkId3 as parseNetworkId,
31380
31637
  parsePkhDid,
31381
31638
  parseSecretCatalogKey,
31382
- parseSpaceUri3 as parseSpaceUri,
31639
+ parseSpaceUri4 as parseSpaceUri,
31383
31640
  pkhDid4 as pkhDid,
31384
31641
  principalDid3 as principalDid,
31385
31642
  principalDidEquals4 as principalDidEquals,