@tinycloud/node-sdk 3.2.0-beta.4 → 3.2.0-beta.6

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.
@@ -329,6 +329,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
329
329
  private _address?;
330
330
  private _chainId?;
331
331
  private _nodeFeatures;
332
+ private _nodeFeaturesKnown;
332
333
  /**
333
334
  * `nodeId` from the `GET /info` performed during sign-in/restore, tagged with
334
335
  * the host it came from. Callers that need the node DID (encryption-network
@@ -383,7 +384,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
383
384
  */
384
385
  setRestoredTinyCloudSession(session: TinyCloudSession, hosts?: string[]): void;
385
386
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
386
- installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[]): void;
387
+ installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[], nodeFeatures?: string[]): void;
387
388
  /**
388
389
  * Ensure `tinycloudHosts` are resolved before a host-needing call.
389
390
  *
@@ -403,6 +404,8 @@ declare class NodeUserAuthorization implements IUserAuthorization {
403
404
  private requireTinyCloudHosts;
404
405
  private get primaryTinyCloudHost();
405
406
  get nodeFeatures(): string[];
407
+ get nodeFeaturesKnown(): boolean;
408
+ refreshNodeInfo(): Promise<void>;
406
409
  /**
407
410
  * The node DID reported by `GET /info` for `host`, if the sign-in (or
408
411
  * restore) that populated it targeted that same host. Returns `undefined`
@@ -895,11 +898,11 @@ interface ValidatedRuntimeDelegation {
895
898
  readonly cid: string;
896
899
  /** The authority object installed by the node runtime. */
897
900
  readonly delegation: PortableDelegation;
898
- /** Capabilities read from the signed compact UCAN attenuation. */
901
+ /** Capabilities read from the signed compact UCAN attenuation or SIWE ReCap. */
899
902
  readonly effectivePermissions: readonly PermissionEntry[];
900
- /** Expiry read from the signed compact UCAN payload. */
903
+ /** Expiry read from the signed compact UCAN payload or SIWE. */
901
904
  readonly expiry: Date;
902
- /** Audience read from the signed compact UCAN payload. */
905
+ /** Audience read from the signed compact UCAN payload or SIWE. */
903
906
  readonly audience: string;
904
907
  /** Host selected for, and used by, the runtime activation. */
905
908
  readonly host: string;
@@ -979,6 +982,20 @@ declare function activateValidatedRuntimeDelegation(node: RuntimeDelegationActiv
979
982
  * derived from signed bytes before `/delegate` is called.
980
983
  */
981
984
  declare function activateCompactRuntimeDelegation(node: RuntimeDelegationActivator, input: CompactRuntimeDelegationInput): Promise<ValidatedRuntimeDelegation>;
985
+ /**
986
+ * Project an OpenKey signed-login delegation from the same verified CACAO
987
+ * authority used when the runtime activates and replays it.
988
+ */
989
+ declare function siweRuntimeDelegation(node: RuntimeDelegationActivator, input: {
990
+ readonly authorization: string;
991
+ readonly cid: string;
992
+ readonly spaceId: string;
993
+ readonly ownerAddress: string;
994
+ readonly chainId: number;
995
+ readonly siwe: string;
996
+ readonly signature: string;
997
+ readonly host: string;
998
+ }): PortableDelegation;
982
999
  /**
983
1000
  * Turn a delegation REQUEST into a signed GRANT.
984
1001
  *
@@ -1474,6 +1491,7 @@ declare class TinyCloudNode {
1474
1491
  * {@link currentTinyCloudSession} as a fallback for `auth.tinyCloudSession`.
1475
1492
  */
1476
1493
  private _restoredTcSession?;
1494
+ private _restoredNodeFeatures?;
1477
1495
  private _activeServiceSession?;
1478
1496
  /**
1479
1497
  * True when the last signIn() detected an interactive signer and skipped
@@ -1504,6 +1522,7 @@ declare class TinyCloudNode {
1504
1522
  warnings?: BootstrapWarning[];
1505
1523
  };
1506
1524
  private get nodeFeatures();
1525
+ private get replicationNodeFeatures();
1507
1526
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
1508
1527
  private get siweDomain();
1509
1528
  private readonly invokeWithRuntimePermissions;
@@ -2736,4 +2755,4 @@ declare class WasmKeyProvider implements KeyProvider {
2736
2755
  */
2737
2756
  declare function createWasmKeyProvider(sessionManager: SessionManagerWithListing): WasmKeyProvider;
2738
2757
 
2739
- export { ACCEPTED_MARKER_VERSIONS as A, BOOTSTRAP_COMPLETION_MARKER_KEY as B, type CompactRuntimeDelegationInput as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, activateValidatedRuntimeDelegation as G, createWasmKeyProvider as H, InvalidRestoredSessionError as I, defaultSignStrategy as J, deserializeDelegation as K, grantAuthRequest as L, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, serializeDelegation as Q, RUNTIME_DELEGATION_REJECTIONS as R, type SecretPermissionHint as S, TinyCloudNode as T, type UnifiedOwnerRootInput as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, signedCompactUcanExpiry as X, type AuthDelegationArtifact as a, type AuthRequestArtifact as b, BOOTSTRAP_COMPLETION_MARKER_VERSION as c, type BootstrapCompletionMarker as d, type BootstrapWarning as e, type CreateOwnerDelegationParams as f, type DelegateToResult as g, DelegatedAccess as h, type DelegationAuthority as i, NodeUserAuthorization as j, type NodeUserAuthorizationConfig as k, type RestorableSession as l, type RuntimeDelegationActivator as m, RuntimeDelegationRejectedError as n, type RuntimeDelegationRejection as o, type RuntimePermissionGrantOptions as p, type SecretReadInput as q, type SecretReadResult as r, type SessionGrantProof as s, type SignStrategy as t, type TinyCloudNodeConfig as u, type UnifiedOwnerRootReceipt as v, UnsupportedSessionRestoreError as w, type VerifiedSessionGrant as x, type WasmKeyProviderConfig as y, activateCompactRuntimeDelegation as z };
2758
+ export { ACCEPTED_MARKER_VERSIONS as A, BOOTSTRAP_COMPLETION_MARKER_KEY as B, type CompactRuntimeDelegationInput as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, activateValidatedRuntimeDelegation as G, createWasmKeyProvider as H, InvalidRestoredSessionError as I, defaultSignStrategy as J, deserializeDelegation as K, grantAuthRequest as L, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, serializeDelegation as Q, RUNTIME_DELEGATION_REJECTIONS as R, type SecretPermissionHint as S, TinyCloudNode as T, type UnifiedOwnerRootInput as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, signedCompactUcanExpiry as X, siweRuntimeDelegation as Y, type AuthDelegationArtifact as a, type AuthRequestArtifact as b, BOOTSTRAP_COMPLETION_MARKER_VERSION as c, type BootstrapCompletionMarker as d, type BootstrapWarning as e, type CreateOwnerDelegationParams as f, type DelegateToResult as g, DelegatedAccess as h, type DelegationAuthority as i, NodeUserAuthorization as j, type NodeUserAuthorizationConfig as k, type RestorableSession as l, type RuntimeDelegationActivator as m, RuntimeDelegationRejectedError as n, type RuntimeDelegationRejection as o, type RuntimePermissionGrantOptions as p, type SecretReadInput as q, type SecretReadResult as r, type SessionGrantProof as s, type SignStrategy as t, type TinyCloudNodeConfig as u, type UnifiedOwnerRootReceipt as v, UnsupportedSessionRestoreError as w, type VerifiedSessionGrant as x, type WasmKeyProviderConfig as y, activateCompactRuntimeDelegation as z };
@@ -329,6 +329,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
329
329
  private _address?;
330
330
  private _chainId?;
331
331
  private _nodeFeatures;
332
+ private _nodeFeaturesKnown;
332
333
  /**
333
334
  * `nodeId` from the `GET /info` performed during sign-in/restore, tagged with
334
335
  * the host it came from. Callers that need the node DID (encryption-network
@@ -383,7 +384,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
383
384
  */
384
385
  setRestoredTinyCloudSession(session: TinyCloudSession, hosts?: string[]): void;
385
386
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
386
- installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[]): void;
387
+ installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[], nodeFeatures?: string[]): void;
387
388
  /**
388
389
  * Ensure `tinycloudHosts` are resolved before a host-needing call.
389
390
  *
@@ -403,6 +404,8 @@ declare class NodeUserAuthorization implements IUserAuthorization {
403
404
  private requireTinyCloudHosts;
404
405
  private get primaryTinyCloudHost();
405
406
  get nodeFeatures(): string[];
407
+ get nodeFeaturesKnown(): boolean;
408
+ refreshNodeInfo(): Promise<void>;
406
409
  /**
407
410
  * The node DID reported by `GET /info` for `host`, if the sign-in (or
408
411
  * restore) that populated it targeted that same host. Returns `undefined`
@@ -895,11 +898,11 @@ interface ValidatedRuntimeDelegation {
895
898
  readonly cid: string;
896
899
  /** The authority object installed by the node runtime. */
897
900
  readonly delegation: PortableDelegation;
898
- /** Capabilities read from the signed compact UCAN attenuation. */
901
+ /** Capabilities read from the signed compact UCAN attenuation or SIWE ReCap. */
899
902
  readonly effectivePermissions: readonly PermissionEntry[];
900
- /** Expiry read from the signed compact UCAN payload. */
903
+ /** Expiry read from the signed compact UCAN payload or SIWE. */
901
904
  readonly expiry: Date;
902
- /** Audience read from the signed compact UCAN payload. */
905
+ /** Audience read from the signed compact UCAN payload or SIWE. */
903
906
  readonly audience: string;
904
907
  /** Host selected for, and used by, the runtime activation. */
905
908
  readonly host: string;
@@ -979,6 +982,20 @@ declare function activateValidatedRuntimeDelegation(node: RuntimeDelegationActiv
979
982
  * derived from signed bytes before `/delegate` is called.
980
983
  */
981
984
  declare function activateCompactRuntimeDelegation(node: RuntimeDelegationActivator, input: CompactRuntimeDelegationInput): Promise<ValidatedRuntimeDelegation>;
985
+ /**
986
+ * Project an OpenKey signed-login delegation from the same verified CACAO
987
+ * authority used when the runtime activates and replays it.
988
+ */
989
+ declare function siweRuntimeDelegation(node: RuntimeDelegationActivator, input: {
990
+ readonly authorization: string;
991
+ readonly cid: string;
992
+ readonly spaceId: string;
993
+ readonly ownerAddress: string;
994
+ readonly chainId: number;
995
+ readonly siwe: string;
996
+ readonly signature: string;
997
+ readonly host: string;
998
+ }): PortableDelegation;
982
999
  /**
983
1000
  * Turn a delegation REQUEST into a signed GRANT.
984
1001
  *
@@ -1474,6 +1491,7 @@ declare class TinyCloudNode {
1474
1491
  * {@link currentTinyCloudSession} as a fallback for `auth.tinyCloudSession`.
1475
1492
  */
1476
1493
  private _restoredTcSession?;
1494
+ private _restoredNodeFeatures?;
1477
1495
  private _activeServiceSession?;
1478
1496
  /**
1479
1497
  * True when the last signIn() detected an interactive signer and skipped
@@ -1504,6 +1522,7 @@ declare class TinyCloudNode {
1504
1522
  warnings?: BootstrapWarning[];
1505
1523
  };
1506
1524
  private get nodeFeatures();
1525
+ private get replicationNodeFeatures();
1507
1526
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
1508
1527
  private get siweDomain();
1509
1528
  private readonly invokeWithRuntimePermissions;
@@ -2736,4 +2755,4 @@ declare class WasmKeyProvider implements KeyProvider {
2736
2755
  */
2737
2756
  declare function createWasmKeyProvider(sessionManager: SessionManagerWithListing): WasmKeyProvider;
2738
2757
 
2739
- export { ACCEPTED_MARKER_VERSIONS as A, BOOTSTRAP_COMPLETION_MARKER_KEY as B, type CompactRuntimeDelegationInput as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, activateValidatedRuntimeDelegation as G, createWasmKeyProvider as H, InvalidRestoredSessionError as I, defaultSignStrategy as J, deserializeDelegation as K, grantAuthRequest as L, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, serializeDelegation as Q, RUNTIME_DELEGATION_REJECTIONS as R, type SecretPermissionHint as S, TinyCloudNode as T, type UnifiedOwnerRootInput as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, signedCompactUcanExpiry as X, type AuthDelegationArtifact as a, type AuthRequestArtifact as b, BOOTSTRAP_COMPLETION_MARKER_VERSION as c, type BootstrapCompletionMarker as d, type BootstrapWarning as e, type CreateOwnerDelegationParams as f, type DelegateToResult as g, DelegatedAccess as h, type DelegationAuthority as i, NodeUserAuthorization as j, type NodeUserAuthorizationConfig as k, type RestorableSession as l, type RuntimeDelegationActivator as m, RuntimeDelegationRejectedError as n, type RuntimeDelegationRejection as o, type RuntimePermissionGrantOptions as p, type SecretReadInput as q, type SecretReadResult as r, type SessionGrantProof as s, type SignStrategy as t, type TinyCloudNodeConfig as u, type UnifiedOwnerRootReceipt as v, UnsupportedSessionRestoreError as w, type VerifiedSessionGrant as x, type WasmKeyProviderConfig as y, activateCompactRuntimeDelegation as z };
2758
+ export { ACCEPTED_MARKER_VERSIONS as A, BOOTSTRAP_COMPLETION_MARKER_KEY as B, type CompactRuntimeDelegationInput as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, activateValidatedRuntimeDelegation as G, createWasmKeyProvider as H, InvalidRestoredSessionError as I, defaultSignStrategy as J, deserializeDelegation as K, grantAuthRequest as L, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, serializeDelegation as Q, RUNTIME_DELEGATION_REJECTIONS as R, type SecretPermissionHint as S, TinyCloudNode as T, type UnifiedOwnerRootInput as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, signedCompactUcanExpiry as X, siweRuntimeDelegation as Y, type AuthDelegationArtifact as a, type AuthRequestArtifact as b, BOOTSTRAP_COMPLETION_MARKER_VERSION as c, type BootstrapCompletionMarker as d, type BootstrapWarning as e, type CreateOwnerDelegationParams as f, type DelegateToResult as g, DelegatedAccess as h, type DelegationAuthority as i, NodeUserAuthorization as j, type NodeUserAuthorizationConfig as k, type RestorableSession as l, type RuntimeDelegationActivator as m, RuntimeDelegationRejectedError as n, type RuntimeDelegationRejection as o, type RuntimePermissionGrantOptions as p, type SecretReadInput as q, type SecretReadResult as r, type SessionGrantProof as s, type SignStrategy as t, type TinyCloudNodeConfig as u, type UnifiedOwnerRootReceipt as v, UnsupportedSessionRestoreError as w, type VerifiedSessionGrant as x, type WasmKeyProviderConfig as y, activateCompactRuntimeDelegation as z };
@@ -1,5 +1,5 @@
1
- import { PermissionEntry, ReplicationOptions as ReplicationOptions$1, TinyCloudSession } from '@tinycloud/sdk-core';
2
- import { ReplicationOptions, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
1
+ import { ReplicationOptions, PermissionEntry, TinyCloudSession } from '@tinycloud/sdk-core';
2
+ import { AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
 
4
4
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
5
5
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
@@ -10,7 +10,7 @@ declare function hasUnrestrictedGetCoverage(entries: readonly PermissionEntry[],
10
10
  declare function augmentSignInEntriesWithReplication(input: {
11
11
  entries: readonly PermissionEntry[];
12
12
  primarySpaceId: string;
13
- replication?: Pick<ReplicationOptions$1, "prefixes" | "allowSecrets">;
13
+ replication?: Pick<ReplicationOptions, "prefixes" | "allowSecrets">;
14
14
  }): PermissionEntry[];
15
15
 
16
16
  /**
@@ -29,9 +29,8 @@ declare function augmentSignInEntriesWithReplication(input: {
29
29
  */
30
30
 
31
31
  /**
32
- * Whether the signed `att` grants `ability` over the selected namespace, with
33
- * each grant unrestricted (no caveats). A bare selector requires both its
34
- * exact key and its slash descendants; a trailing-slash selector is one range.
32
+ * Whether the signed `att` grants `ability` over the folder prefix, with
33
+ * unrestricted authority unless explicitly asked to ignore caveats.
35
34
  */
36
35
  declare function ucanAttCovers(att: Record<string, Record<string, unknown>>, space: string, prefix: string, ability: string, options?: {
37
36
  ignoreCaveats?: boolean;
@@ -1,5 +1,5 @@
1
- import { PermissionEntry, ReplicationOptions as ReplicationOptions$1, TinyCloudSession } from '@tinycloud/sdk-core';
2
- import { ReplicationOptions, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
1
+ import { ReplicationOptions, PermissionEntry, TinyCloudSession } from '@tinycloud/sdk-core';
2
+ import { AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
 
4
4
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
5
5
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
@@ -10,7 +10,7 @@ declare function hasUnrestrictedGetCoverage(entries: readonly PermissionEntry[],
10
10
  declare function augmentSignInEntriesWithReplication(input: {
11
11
  entries: readonly PermissionEntry[];
12
12
  primarySpaceId: string;
13
- replication?: Pick<ReplicationOptions$1, "prefixes" | "allowSecrets">;
13
+ replication?: Pick<ReplicationOptions, "prefixes" | "allowSecrets">;
14
14
  }): PermissionEntry[];
15
15
 
16
16
  /**
@@ -29,9 +29,8 @@ declare function augmentSignInEntriesWithReplication(input: {
29
29
  */
30
30
 
31
31
  /**
32
- * Whether the signed `att` grants `ability` over the selected namespace, with
33
- * each grant unrestricted (no caveats). A bare selector requires both its
34
- * exact key and its slash descendants; a trailing-slash selector is one range.
32
+ * Whether the signed `att` grants `ability` over the folder prefix, with
33
+ * unrestricted authority unless explicitly asked to ignore caveats.
35
34
  */
36
35
  declare function ucanAttCovers(att: Record<string, Record<string, unknown>>, space: string, prefix: string, ability: string, options?: {
37
36
  ignoreCaveats?: boolean;
package/dist/core.cjs CHANGED
@@ -347,13 +347,13 @@ var import_sdk_core4 = require("@tinycloud/sdk-core");
347
347
  // src/replication/authority-sign-in.ts
348
348
  var import_sdk_core2 = require("@tinycloud/sdk-core");
349
349
  var import_sdk_services = require("@tinycloud/sdk-services");
350
+ var import_prefix = require("@tinycloud/sdk-services/kv/replication/prefix");
350
351
  var KV_GET = import_sdk_core2.KV.GET;
351
352
  var KV_SYNC = import_sdk_core2.KV.SYNC;
352
353
  var KV_SERVICE = "tinycloud.kv";
353
354
  function hasUnrestrictedGetCoverage(entries, spaceId, prefix) {
354
- const requiredPaths = (0, import_sdk_core2.grantPathsForSelector)(prefix);
355
355
  return (0, import_sdk_core2.isCapabilitySubset)(
356
- requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, actions: [KV_GET] })),
356
+ [{ service: KV_SERVICE, space: spaceId, path: prefix, actions: [KV_GET] }],
357
357
  [...entries]
358
358
  ).subset;
359
359
  }
@@ -361,21 +361,33 @@ function augmentSignInEntriesWithReplication(input) {
361
361
  const out = [];
362
362
  const replication = input.replication;
363
363
  if (replication === void 0) return out;
364
+ const eligiblePaths = [];
364
365
  for (const prefix of replication.prefixes) {
366
+ (0, import_prefix.assertFolderPrefix)(prefix);
367
+ const path = prefix === "/" ? "" : prefix;
365
368
  if ((0, import_sdk_services.requiresSecretsOptIn)(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
366
369
  continue;
367
370
  }
368
- if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {
371
+ if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, path)) {
369
372
  continue;
370
373
  }
371
- for (const path of (0, import_sdk_core2.grantPathsForSelector)(prefix)) {
372
- out.push({
373
- service: KV_SERVICE,
374
- space: input.primarySpaceId,
375
- path,
376
- actions: [KV_GET, KV_SYNC]
377
- });
378
- }
374
+ eligiblePaths.push(path);
375
+ }
376
+ if (eligiblePaths.length === replication.prefixes.length && eligiblePaths.length > 0 && hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, "")) {
377
+ return [{
378
+ service: KV_SERVICE,
379
+ space: input.primarySpaceId,
380
+ path: "",
381
+ actions: [KV_GET, KV_SYNC]
382
+ }];
383
+ }
384
+ for (const path of eligiblePaths) {
385
+ out.push({
386
+ service: KV_SERVICE,
387
+ space: input.primarySpaceId,
388
+ path,
389
+ actions: [KV_GET, KV_SYNC]
390
+ });
379
391
  }
380
392
  return out;
381
393
  }
@@ -444,6 +456,7 @@ var NodeUserAuthorization = class {
444
456
  this.activeSessionKeyId = "default";
445
457
  this.extensions = [];
446
458
  this._nodeFeatures = [];
459
+ this._nodeFeaturesKnown = false;
447
460
  this._lastActivationSkippedSpaceIds = [];
448
461
  this.wasm = config.wasmBindings;
449
462
  this.signer = config.signer;
@@ -567,8 +580,11 @@ var NodeUserAuthorization = class {
567
580
  }
568
581
  }
569
582
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
570
- installRestoredSession(sessionManager, session, hosts) {
583
+ installRestoredSession(sessionManager, session, hosts, nodeFeatures) {
571
584
  this.sessionManager = sessionManager;
585
+ this._nodeFeatures = nodeFeatures === void 0 ? [] : [...nodeFeatures];
586
+ this._nodeFeaturesKnown = nodeFeatures !== void 0;
587
+ this._nodeInfoIdentity = void 0;
572
588
  this.tinycloudHosts = hosts && hosts.length > 0 ? [...hosts] : void 0;
573
589
  this._lastActivationSkippedSpaceIds = [];
574
590
  if (!session) {
@@ -643,6 +659,16 @@ var NodeUserAuthorization = class {
643
659
  get nodeFeatures() {
644
660
  return this._nodeFeatures;
645
661
  }
662
+ get nodeFeaturesKnown() {
663
+ return this._nodeFeaturesKnown;
664
+ }
665
+ async refreshNodeInfo() {
666
+ const infoHost = this.primaryTinyCloudHost;
667
+ const nodeInfo = await (0, import_sdk_core4.checkNodeInfo)(infoHost, this.wasm.protocolVersion());
668
+ this._nodeFeatures = nodeInfo.features;
669
+ this._nodeFeaturesKnown = true;
670
+ this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
671
+ }
646
672
  /**
647
673
  * The node DID reported by `GET /info` for `host`, if the sign-in (or
648
674
  * restore) that populated it targeted that same host. Returns `undefined`
@@ -1306,13 +1332,7 @@ var NodeUserAuthorization = class {
1306
1332
  this._tinyCloudSession = tinyCloudSession;
1307
1333
  this._address = address;
1308
1334
  this._chainId = chainId;
1309
- const infoHost = this.primaryTinyCloudHost;
1310
- const nodeInfo = await (0, import_sdk_core4.checkNodeInfo)(
1311
- infoHost,
1312
- this.wasm.protocolVersion()
1313
- );
1314
- this._nodeFeatures = nodeInfo.features;
1315
- this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
1335
+ await this.refreshNodeInfo();
1316
1336
  for (const ext of this.extensions) {
1317
1337
  if (ext.afterSignIn) {
1318
1338
  await ext.afterSignIn(clientSession);
@@ -1478,13 +1498,7 @@ var NodeUserAuthorization = class {
1478
1498
  this._tinyCloudSession = tinyCloudSession;
1479
1499
  this._address = address;
1480
1500
  this._chainId = chainId;
1481
- const infoHost = this.primaryTinyCloudHost;
1482
- const nodeInfo = await (0, import_sdk_core4.checkNodeInfo)(
1483
- infoHost,
1484
- this.wasm.protocolVersion()
1485
- );
1486
- this._nodeFeatures = nodeInfo.features;
1487
- this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
1501
+ await this.refreshNodeInfo();
1488
1502
  for (const ext of this.extensions) {
1489
1503
  if (ext.afterSignIn) {
1490
1504
  await ext.afterSignIn(clientSession);
@@ -2394,18 +2408,25 @@ async function activateCompactRuntimeDelegation(node, input) {
2394
2408
  throw new Error("Compact runtime delegation CID does not match authorization bytes.");
2395
2409
  }
2396
2410
  const signed = signedAuthorityFromCompactUcan(authorization);
2411
+ return activateValidatedRuntimeDelegation(
2412
+ node,
2413
+ portableFromSignedAuthority(cid, authorization, signed, input),
2414
+ { host: input.host }
2415
+ );
2416
+ }
2417
+ function portableFromSignedAuthority(cid, authorization, signed, transport) {
2397
2418
  const primary = signed.resources[0];
2398
2419
  const signedSpace = signed.permissions.find(
2399
2420
  (permission) => permission.service !== "tinycloud.encryption"
2400
2421
  )?.space;
2401
- const delegation = {
2422
+ return {
2402
2423
  cid,
2403
2424
  delegationHeader: { Authorization: authorization },
2404
- ownerAddress: input.ownerAddress,
2405
- chainId: input.chainId,
2425
+ ownerAddress: transport.ownerAddress,
2426
+ chainId: transport.chainId,
2406
2427
  delegatorDID: signed.issuer,
2407
2428
  authHeader: authorization,
2408
- spaceId: signedSpace ?? primary.space,
2429
+ spaceId: signedSpace ?? transport.spaceId ?? primary.space,
2409
2430
  path: primary.path,
2410
2431
  actions: [...primary.actions],
2411
2432
  ...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
@@ -2415,11 +2436,8 @@ async function activateCompactRuntimeDelegation(node, input) {
2415
2436
  })),
2416
2437
  expiry: new Date(signed.expiry),
2417
2438
  delegateDID: signed.audience,
2418
- host: normalizedHost(input.host)
2439
+ host: normalizedHost(transport.host)
2419
2440
  };
2420
- return activateValidatedRuntimeDelegation(node, delegation, {
2421
- host: input.host
2422
- });
2423
2441
  }
2424
2442
  function serializeDelegation(delegation) {
2425
2443
  return JSON.stringify({
@@ -2437,7 +2455,7 @@ function deserializeDelegation(data) {
2437
2455
  }
2438
2456
 
2439
2457
  // src/replication/config.ts
2440
- var import_sdk_services2 = require("@tinycloud/sdk-services");
2458
+ var import_prefix2 = require("@tinycloud/sdk-services/kv/replication/prefix");
2441
2459
  function assertValidReplicationConfig(replication) {
2442
2460
  if (replication === void 0 || replication.enabled !== true) return;
2443
2461
  if (replication.storage === void 0 || replication.storage === null) {
@@ -2447,19 +2465,21 @@ function assertValidReplicationConfig(replication) {
2447
2465
  throw new TypeError("replication.prefixes must be a non-empty array");
2448
2466
  }
2449
2467
  for (const prefix of replication.prefixes) {
2450
- if (typeof prefix !== "string" || prefix === "") {
2451
- throw new TypeError("replication.prefixes must not contain an empty prefix");
2468
+ try {
2469
+ (0, import_prefix2.assertFolderPrefix)(prefix);
2470
+ } catch (error) {
2471
+ throw new TypeError(error instanceof Error ? error.message : String(error));
2452
2472
  }
2453
2473
  }
2454
2474
  for (const [index, prefix] of replication.prefixes.entries()) {
2455
2475
  for (const other of replication.prefixes.slice(index + 1)) {
2456
- if ((0, import_sdk_services2.kvPrefixCovers)(prefix, other) || (0, import_sdk_services2.kvPrefixCovers)(other, prefix)) {
2476
+ if ((0, import_prefix2.kvPrefixCovers)(prefix, other) || (0, import_prefix2.kvPrefixCovers)(other, prefix)) {
2457
2477
  throw new TypeError(
2458
2478
  `replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
2459
2479
  );
2460
2480
  }
2461
2481
  }
2462
- if (prefix.split("/", 1)[0] === "vault" && replication.allowSecrets !== true) {
2482
+ if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
2463
2483
  throw new TypeError(
2464
2484
  `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
2465
2485
  );
@@ -2469,7 +2489,7 @@ function assertValidReplicationConfig(replication) {
2469
2489
 
2470
2490
  // src/TinyCloudNode.ts
2471
2491
  var import_sdk_core10 = require("@tinycloud/sdk-core");
2472
- var import_sdk_services3 = require("@tinycloud/sdk-services");
2492
+ var import_sdk_services2 = require("@tinycloud/sdk-services");
2473
2493
 
2474
2494
  // src/account/AccountService.ts
2475
2495
  var import_sdk_core6 = require("@tinycloud/sdk-core");
@@ -3567,7 +3587,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3567
3587
  return this._bootstrapStatus;
3568
3588
  }
3569
3589
  get nodeFeatures() {
3570
- return this.auth?.nodeFeatures ?? [];
3590
+ return this.auth?.nodeFeatures ?? this._restoredNodeFeatures ?? [];
3591
+ }
3592
+ get replicationNodeFeatures() {
3593
+ if (this.auth) return this.auth.nodeFeaturesKnown ? this.auth.nodeFeatures : void 0;
3594
+ return this._restoredNodeFeatures;
3571
3595
  }
3572
3596
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
3573
3597
  get siweDomain() {
@@ -4920,6 +4944,14 @@ var _TinyCloudNode = class _TinyCloudNode {
4920
4944
  sessionData.chainId
4921
4945
  );
4922
4946
  const stagedHost = resolvedHost ?? this.config.host;
4947
+ let restoredNodeFeatures;
4948
+ if (this.config.replication?.enabled) {
4949
+ try {
4950
+ restoredNodeFeatures = (await (0, import_sdk_core10.checkNodeInfo)(stagedHost, this.wasmBindings.protocolVersion())).features;
4951
+ } catch (error) {
4952
+ if (!(error instanceof import_sdk_core10.VersionCheckError)) throw error;
4953
+ }
4954
+ }
4923
4955
  const stagedAddress = restoredAddress;
4924
4956
  const stagedChainId = sessionData.chainId ?? 1;
4925
4957
  let replicationPrincipal;
@@ -5022,8 +5054,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5022
5054
  this._recapOperationsCache = stagedPrimary.cache;
5023
5055
  this.runtimePermissionGrants = stagedPrimary.grants;
5024
5056
  this.confirmedHostedSpaceIds.clear();
5057
+ this._restoredNodeFeatures = restoredNodeFeatures;
5025
5058
  if (this.auth) {
5026
- this.auth.installRestoredSession(stagedManager, stagedTcSession, [stagedHost]);
5059
+ this.auth.installRestoredSession(stagedManager, stagedTcSession, [stagedHost], restoredNodeFeatures);
5027
5060
  this._restoredTcSession = void 0;
5028
5061
  } else {
5029
5062
  this._restoredTcSession = stagedTcSession;
@@ -5853,7 +5886,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5853
5886
  );
5854
5887
  graph.assertActive();
5855
5888
  if (!response.ok) {
5856
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services3.parsePermissionHint)({
5889
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services2.parsePermissionHint)({
5857
5890
  service: "tinycloud.encryption",
5858
5891
  path: networkId,
5859
5892
  actions: ["tinycloud.encryption/decrypt"]
@@ -6621,7 +6654,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6621
6654
  );
6622
6655
  if (result.status !== "ok") {
6623
6656
  if (result.status === "permission_required") {
6624
- const hint = (0, import_sdk_services3.parsePermissionHint)(result.hint);
6657
+ const hint = (0, import_sdk_services2.parsePermissionHint)(result.hint);
6625
6658
  if (hint === void 0) return { status: "read_failed" };
6626
6659
  return {
6627
6660
  status: "permission_required",
@@ -7597,6 +7630,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7597
7630
  session,
7598
7631
  principal: identityPrincipal,
7599
7632
  authority,
7633
+ nodeFeatures: this.replicationNodeFeatures,
7600
7634
  primaryKV: [{ space: session.spaceId, kv }]
7601
7635
  });
7602
7636
  }