@tinycloud/node-sdk 3.2.6-beta.6 → 3.3.0-beta.8

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 (43) hide show
  1. package/dist/{WasmKeyProvider-DEdSyQ0I.d.cts → TinyCloudNode-CcNh20QX.d.cts} +181 -286
  2. package/dist/{WasmKeyProvider-DEdSyQ0I.d.ts → TinyCloudNode-CcNh20QX.d.ts} +181 -286
  3. package/dist/WasmKeyProvider-5Ee7fcAw.d.cts +217 -0
  4. package/dist/WasmKeyProvider-5Ee7fcAw.d.ts +217 -0
  5. package/dist/authority-Cwotwza3.d.cts +118 -0
  6. package/dist/authority-D_u9a7Wl.d.ts +118 -0
  7. package/dist/core.cjs +399 -167
  8. package/dist/core.cjs.map +1 -1
  9. package/dist/core.d.cts +37 -4
  10. package/dist/core.d.ts +37 -4
  11. package/dist/core.js +406 -175
  12. package/dist/core.js.map +1 -1
  13. package/dist/gated-network-BEF2XSTC.d.cts +16 -0
  14. package/dist/gated-network-BEF2XSTC.d.ts +16 -0
  15. package/dist/index.cjs +2733 -544
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +35 -89
  18. package/dist/index.d.ts +35 -89
  19. package/dist/index.js +2728 -544
  20. package/dist/index.js.map +1 -1
  21. package/dist/replication/authority.cjs +153 -15
  22. package/dist/replication/authority.cjs.map +1 -1
  23. package/dist/replication/authority.d.cts +4 -1
  24. package/dist/replication/authority.d.ts +4 -1
  25. package/dist/replication/authority.js +147 -14
  26. package/dist/replication/authority.js.map +1 -1
  27. package/dist/replication/gated-network.cjs +90 -0
  28. package/dist/replication/gated-network.cjs.map +1 -0
  29. package/dist/replication/gated-network.d.cts +2 -0
  30. package/dist/replication/gated-network.d.ts +2 -0
  31. package/dist/replication/gated-network.js +65 -0
  32. package/dist/replication/gated-network.js.map +1 -0
  33. package/dist/replication/runtime.cjs +84 -6
  34. package/dist/replication/runtime.cjs.map +1 -1
  35. package/dist/replication/runtime.d.cts +2 -1
  36. package/dist/replication/runtime.d.ts +2 -1
  37. package/dist/replication/runtime.js +84 -6
  38. package/dist/replication/runtime.js.map +1 -1
  39. package/dist/{runtime-B5P5SRpC.d.ts → runtime-D1o9Eybt.d.cts} +14 -3
  40. package/dist/{runtime-B5P5SRpC.d.cts → runtime-v62FzQHz.d.ts} +14 -3
  41. package/package.json +14 -4
  42. package/dist/authority-DDGtccBK.d.cts +0 -96
  43. package/dist/authority-DDGtccBK.d.ts +0 -96
package/dist/core.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  makePkhSpaceId as makePkhSpaceId2,
31
31
  parsePkhDid,
32
32
  pkhDid as pkhDid3,
33
- principalDid as principalDid2,
33
+ principalDid,
34
34
  principalDidEquals as principalDidEquals4,
35
35
  parseCanonicalNetworkId,
36
36
  TinyCloudDebugLogger,
@@ -454,6 +454,7 @@ var NodeUserAuthorization = class {
454
454
  this.siweConfig = config.siweConfig;
455
455
  this.includeAccountRegistryPermissions = config.includeAccountRegistryPermissions ?? true;
456
456
  this.replication = config.replication?.enabled === true ? config.replication : void 0;
457
+ this.establishReplicaStamp = config.establishReplicaStamp;
457
458
  this._manifest = config.manifest;
458
459
  this._capabilityRequest = config.capabilityRequest;
459
460
  this.sessionManager = config.sessionManager ?? this.wasm.createSessionManager();
@@ -1223,6 +1224,27 @@ var NodeUserAuthorization = class {
1223
1224
  signature
1224
1225
  };
1225
1226
  }
1227
+ persistedData(session, expiresAt, createdAt, replicaStamp) {
1228
+ return {
1229
+ address: session.address,
1230
+ chainId: session.chainId,
1231
+ sessionKey: JSON.stringify(session.jwk),
1232
+ siwe: session.siwe,
1233
+ signature: session.signature,
1234
+ tinycloudSession: {
1235
+ delegationHeader: session.delegationHeader,
1236
+ delegationCid: session.delegationCid,
1237
+ spaceId: session.spaceId,
1238
+ spaces: session.spaces,
1239
+ verificationMethod: session.verificationMethod
1240
+ },
1241
+ expiresAt,
1242
+ createdAt,
1243
+ version: "1.0",
1244
+ tinycloudHosts: this.tinycloudHosts,
1245
+ ...replicaStamp ? { replicaStamp } : {}
1246
+ };
1247
+ }
1226
1248
  /**
1227
1249
  * Sign in and create a new session.
1228
1250
  *
@@ -1302,24 +1324,13 @@ var NodeUserAuthorization = class {
1302
1324
  siwe: prepared.siwe,
1303
1325
  signature
1304
1326
  };
1305
- const persistedData = {
1306
- address,
1307
- chainId,
1308
- sessionKey: JSON.stringify(jwk),
1309
- siwe: prepared.siwe,
1310
- signature,
1311
- tinycloudSession: {
1312
- delegationHeader: session.delegationHeader,
1313
- delegationCid: session.delegationCid,
1314
- spaceId,
1315
- spaces: spacesMetadata,
1316
- verificationMethod: this.sessionManager.getDID(keyId)
1317
- },
1318
- expiresAt: expirationTime.toISOString(),
1319
- createdAt: now.toISOString(),
1320
- version: "1.0",
1321
- tinycloudHosts: this.tinycloudHosts
1322
- };
1327
+ const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
1328
+ const persistedData = this.persistedData(
1329
+ tinyCloudSession,
1330
+ expirationTime.toISOString(),
1331
+ now.toISOString(),
1332
+ replicaStamp
1333
+ );
1323
1334
  await this.enqueuePersistence(async () => {
1324
1335
  await this.sessionStorage.save(address, persistedData);
1325
1336
  if (generation !== this.sessionGeneration) {
@@ -1473,28 +1484,12 @@ var NodeUserAuthorization = class {
1473
1484
  siwe: prepared.siwe,
1474
1485
  signature
1475
1486
  };
1476
- const expirationMatch = prepared.siwe.match(/Expiration Time: (.+)/);
1477
- const issuedAtMatch = prepared.siwe.match(/Issued At: (.+)/);
1487
+ const expirationMatch = prepared.siwe?.match(/Expiration Time: (.+)/);
1488
+ const issuedAtMatch = prepared.siwe?.match(/Issued At: (.+)/);
1478
1489
  const expiresAt = expirationMatch?.[1] ?? new Date(Date.now() + this.sessionExpirationMs).toISOString();
1479
1490
  const createdAt = issuedAtMatch?.[1] ?? (/* @__PURE__ */ new Date()).toISOString();
1480
- const persistedData = {
1481
- address,
1482
- chainId,
1483
- sessionKey: JSON.stringify(jwk),
1484
- siwe: prepared.siwe,
1485
- signature,
1486
- tinycloudSession: {
1487
- delegationHeader: session.delegationHeader,
1488
- delegationCid: session.delegationCid,
1489
- spaceId: prepared.spaceId,
1490
- spaces: spacesMetadata,
1491
- verificationMethod: this.sessionManager.getDID(keyId)
1492
- },
1493
- expiresAt,
1494
- createdAt,
1495
- version: "1.0",
1496
- tinycloudHosts: this.tinycloudHosts
1497
- };
1491
+ const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
1492
+ const persistedData = this.persistedData(tinyCloudSession, expiresAt, createdAt, replicaStamp);
1498
1493
  await this.sessionStorage.save(address, persistedData);
1499
1494
  this._session = clientSession;
1500
1495
  this._tinyCloudSession = tinyCloudSession;
@@ -1557,15 +1552,36 @@ var NodeUserAuthorization = class {
1557
1552
  const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
1558
1553
  const presentation = this.buildPresentationEnvelope(options?.reason);
1559
1554
  const hostHint = this.primaryTinyCloudHost;
1560
- const result = await authorizeFn({
1555
+ const originalRequest = {
1561
1556
  protocolVersion: 1,
1562
1557
  siwe: prepared.siwe,
1563
1558
  jwk,
1559
+ verificationMethod: prepared.verificationMethod,
1564
1560
  host: hostHint,
1565
1561
  keyId: options?.openkeyKeyId,
1566
1562
  presentation
1567
- });
1568
- return this.signInWithOpenKeyResult(result, prepared, keyId, jwk);
1563
+ };
1564
+ let result;
1565
+ let signed;
1566
+ let signature;
1567
+ for (let attempt = 0; ; ) {
1568
+ try {
1569
+ result ?? (result = await authorizeFn(originalRequest));
1570
+ signed = await this.verifyOpenKeyResult(result, prepared);
1571
+ signature = result.signature;
1572
+ break;
1573
+ } catch (error) {
1574
+ if (!options?.retryResult) throw error;
1575
+ const next = await options.retryResult(
1576
+ error instanceof Error ? error : new Error(String(error)),
1577
+ originalRequest,
1578
+ ++attempt
1579
+ );
1580
+ if (next === null) throw error;
1581
+ result = next;
1582
+ }
1583
+ }
1584
+ return this.signInWithPreparedSession(signed, signature, keyId, jwk);
1569
1585
  }
1570
1586
  /**
1571
1587
  * Build a `CapabilityPresentationEnvelopeV1` from `this._manifest`. The
@@ -1645,6 +1661,11 @@ var NodeUserAuthorization = class {
1645
1661
  * (resource, action) pair in the returned SIWE.
1646
1662
  */
1647
1663
  async signInWithOpenKeyResult(result, prepared, keyId, jwk) {
1664
+ const signed = await this.verifyOpenKeyResult(result, prepared);
1665
+ return this.signInWithPreparedSession(signed, result.signature, keyId, jwk);
1666
+ }
1667
+ /** Validate a candidate without activating a network session, so paste retries reuse one preparation. */
1668
+ async verifyOpenKeyResult(result, prepared) {
1648
1669
  if (result.protocolVersion !== 1) {
1649
1670
  throw new Error(`Unsupported OpenKey protocol version ${result.protocolVersion}`);
1650
1671
  }
@@ -1889,17 +1910,12 @@ var NodeUserAuthorization = class {
1889
1910
  }
1890
1911
  }
1891
1912
  void nonRequiredGrantedPairs;
1892
- return this.signInWithPreparedSession(
1893
- {
1894
- siwe: result.signedMessage,
1895
- jwk: prepared.jwk,
1896
- spaceId: prepared.spaceId,
1897
- verificationMethod: prepared.verificationMethod
1898
- },
1899
- result.signature,
1900
- keyId,
1901
- jwk
1902
- );
1913
+ return {
1914
+ siwe: result.signedMessage,
1915
+ jwk: prepared.jwk,
1916
+ spaceId: prepared.spaceId,
1917
+ verificationMethod: prepared.verificationMethod
1918
+ };
1903
1919
  }
1904
1920
  /**
1905
1921
  * Clear persisted session data.
@@ -2462,45 +2478,10 @@ function deserializeDelegation(data) {
2462
2478
 
2463
2479
  // src/TinyCloudNode.ts
2464
2480
  import { generateNonce, SiweMessage as SiweMessage3 } from "siwe";
2465
-
2466
- // src/replication/config.ts
2467
- import { assertFolderPrefix as assertPureFolderPrefix, kvPrefixCovers } from "@tinycloud/sdk-services/kv/replication/prefix";
2468
- function assertValidReplicationConfig(replication) {
2469
- if (replication === void 0 || replication.enabled !== true) return;
2470
- if (replication.storage === void 0 || replication.storage === null) {
2471
- throw new TypeError("replication.enabled requires replication.storage");
2472
- }
2473
- if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
2474
- throw new TypeError("replication.prefixes must be a non-empty array");
2475
- }
2476
- for (const prefix of replication.prefixes) {
2477
- try {
2478
- assertPureFolderPrefix(prefix);
2479
- } catch (error) {
2480
- throw new TypeError(error instanceof Error ? error.message : String(error));
2481
- }
2482
- }
2483
- for (const [index, prefix] of replication.prefixes.entries()) {
2484
- for (const other of replication.prefixes.slice(index + 1)) {
2485
- if (kvPrefixCovers(prefix, other) || kvPrefixCovers(other, prefix)) {
2486
- throw new TypeError(
2487
- `replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
2488
- );
2489
- }
2490
- }
2491
- if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
2492
- throw new TypeError(
2493
- `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
2494
- );
2495
- }
2496
- }
2497
- }
2498
-
2499
- // src/TinyCloudNode.ts
2500
2481
  import {
2501
2482
  TinyCloud,
2502
2483
  checkNodeInfo as checkNodeInfo2,
2503
- VersionCheckError,
2484
+ ProtocolMismatchError,
2504
2485
  activateSessionWithHost as activateSessionWithHost2,
2505
2486
  authorizationVerdictOf,
2506
2487
  httpResponseError,
@@ -2551,7 +2532,6 @@ import {
2551
2532
  verifyDidKeyEd25519Signature,
2552
2533
  canonicalizeAddress as canonicalizeAddress2,
2553
2534
  pkhDid as pkhDid2,
2554
- principalDid,
2555
2535
  resolveTinyCloudHosts as resolveTinyCloudHosts2,
2556
2536
  publishLocationRecord,
2557
2537
  principalDidEquals as principalDidEquals3,
@@ -2562,6 +2542,7 @@ import {
2562
2542
  validateShareDeliveryAuthorizationV3Bytes,
2563
2543
  verifyEip191MessageSignature,
2564
2544
  signCompactUcanRootAuthorization,
2545
+ markDelegationTerminal,
2565
2546
  isStorageFullError,
2566
2547
  STORAGE_FULL_MESSAGE,
2567
2548
  STORAGE_WRITE_TOO_LARGE_MESSAGE,
@@ -3388,6 +3369,7 @@ function ownerPermissionsToAbilities(permissions) {
3388
3369
  }
3389
3370
  return { abilities, rawAbilities };
3390
3371
  }
3372
+ var TERMINAL_DELEGATION_REQUIRES_SESSION_KEY = "disableSubDelegation requires a session-key delegation";
3391
3373
  function base64UrlEncode(bytes) {
3392
3374
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3393
3375
  let output = "";
@@ -3499,6 +3481,20 @@ function accountIndexWarningDiagnostic(error) {
3499
3481
  }
3500
3482
  return diagnostic;
3501
3483
  }
3484
+ function replicaRestoreRefusal(error) {
3485
+ if (error !== null && typeof error === "object" && "code" in error && error.code === "REPLICA_GATED" && "gate" in error) {
3486
+ switch (error.gate) {
3487
+ case "not_established":
3488
+ case "stamp_stale":
3489
+ case "principal_mismatch":
3490
+ case "purge_pending":
3491
+ case "logout_pending":
3492
+ case "gate_unavailable":
3493
+ return error.gate;
3494
+ }
3495
+ }
3496
+ return "gate_unavailable";
3497
+ }
3502
3498
  var _TinyCloudNode = class _TinyCloudNode {
3503
3499
  /**
3504
3500
  * Create a new TinyCloudNode instance.
@@ -3528,6 +3524,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3528
3524
  this.auth = null;
3529
3525
  this.tc = null;
3530
3526
  this._chainId = 1;
3527
+ /** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
3528
+ this.establishReplicaStamp = async (session) => {
3529
+ const replication = this.config.replication;
3530
+ if (!replication?.enabled) return void 0;
3531
+ const epoch = this.sessionEpoch;
3532
+ const attempt = this.latestSessionAttempt;
3533
+ try {
3534
+ const { verifiedOwnerConsentAnchor, establishReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
3535
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
3536
+ const anchor = verifiedOwnerConsentAnchor(session, this.wasmBindings, exactVerifiedRecap);
3537
+ if (anchor === null) {
3538
+ throw Object.assign(new Error("Replica sign-in requires a verified finite owner SIWE and exact-space KV get+sync."), {
3539
+ code: "REPLICA_GATED",
3540
+ gate: "not_established"
3541
+ });
3542
+ }
3543
+ const stamp = await establishReplicaSessionStamp(
3544
+ replication.storage,
3545
+ pkhDid2(session.address, session.chainId),
3546
+ anchor
3547
+ );
3548
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
3549
+ this.replicaSessionStamp = stamp;
3550
+ return stamp;
3551
+ } catch (error) {
3552
+ if (epoch === this.sessionEpoch && attempt === this.latestSessionAttempt) {
3553
+ this.replicaRestoreGate = replicaRestoreRefusal(error);
3554
+ }
3555
+ return void 0;
3556
+ }
3557
+ };
3531
3558
  this._baseVaults = /* @__PURE__ */ new Map();
3532
3559
  this._baseSecrets = /* @__PURE__ */ new Map();
3533
3560
  this._secrets = /* @__PURE__ */ new Map();
@@ -3652,7 +3679,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3652
3679
  });
3653
3680
  return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
3654
3681
  };
3655
- assertValidReplicationConfig(config.replication);
3682
+ if (config.replication?.enabled) getNodeReplicationLoaders().validateConfig(config.replication);
3656
3683
  this.explicitHost = config.host;
3657
3684
  this.config = {
3658
3685
  ...config,
@@ -3667,6 +3694,16 @@ var _TinyCloudNode = class _TinyCloudNode {
3667
3694
  this.replicationRuntime = runtime;
3668
3695
  this.replicationControl = {
3669
3696
  status: async () => {
3697
+ const gate = this.replicaRestoreGate;
3698
+ if (gate) return this.config.replication.prefixes.map((prefix) => ({
3699
+ prefix,
3700
+ state: "gated",
3701
+ gate,
3702
+ errorCode: "REPLICA_GATED",
3703
+ pending: { inFlight: 0, committed: 0, ambiguous: 0 },
3704
+ pinned: [],
3705
+ lagMs: null
3706
+ }));
3670
3707
  const status = await runtime.then((ready) => ready.control.status());
3671
3708
  const scopeError = this.replicationScopeError;
3672
3709
  if (!scopeError) return status;
@@ -3683,7 +3720,15 @@ var _TinyCloudNode = class _TinyCloudNode {
3683
3720
  lagMs: null
3684
3721
  }));
3685
3722
  },
3686
- sync: async (input) => (await runtime).control.sync(input),
3723
+ sync: async (input) => {
3724
+ if (this.replicaRestoreGate) {
3725
+ throw Object.assign(new Error("Replica restore is gated."), {
3726
+ code: "REPLICA_GATED",
3727
+ gate: this.replicaRestoreGate
3728
+ });
3729
+ }
3730
+ return (await runtime).control.sync(input);
3731
+ },
3687
3732
  purge: (input) => {
3688
3733
  const instance = this.replicationRuntimeInstance;
3689
3734
  return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
@@ -3899,6 +3944,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3899
3944
  signer: this.signer,
3900
3945
  signStrategy: config.signStrategy ?? { type: "auto-sign" },
3901
3946
  wasmBindings: this.wasmBindings,
3947
+ ...config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
3902
3948
  sessionManager: this.sessionManager,
3903
3949
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
3904
3950
  domain: this.siweDomain,
@@ -4096,11 +4142,10 @@ var _TinyCloudNode = class _TinyCloudNode {
4096
4142
  return this.signer === null;
4097
4143
  }
4098
4144
  /**
4099
- * Get the space ID for this user.
4100
- * Available after signIn().
4145
+ * Primary space of the signed-in or restored network session, including compact sessions.
4101
4146
  */
4102
4147
  get spaceId() {
4103
- return this.auth?.tinyCloudSession?.spaceId;
4148
+ return this.auth?.tinyCloudSession?.spaceId ?? this._activeServiceSession?.spaceId;
4104
4149
  }
4105
4150
  /**
4106
4151
  * Get the account space ID for this wallet identity.
@@ -4148,6 +4193,12 @@ var _TinyCloudNode = class _TinyCloudNode {
4148
4193
  get restorableSession() {
4149
4194
  return this.currentTinyCloudSession();
4150
4195
  }
4196
+ /** The active, verified replica stamp. Undefined when the replica is refused or the session has ended. */
4197
+ get replicaStamp() {
4198
+ const stamp = this.replicaSessionStamp;
4199
+ if (!stamp || this.replicaRestoreGate !== void 0 || !this.currentTinyCloudSession()) return void 0;
4200
+ return { incarnation: stamp.incarnation, lifecycleEpoch: stamp.lifecycleEpoch };
4201
+ }
4151
4202
  /**
4152
4203
  * Sign out and end the session on this instance.
4153
4204
  *
@@ -4168,19 +4219,61 @@ var _TinyCloudNode = class _TinyCloudNode {
4168
4219
  }
4169
4220
  async endSession() {
4170
4221
  const leavingAddress = this.address ?? this._restoredTcSession?.address;
4222
+ const leavingChainId = this._chainId;
4223
+ const leavingPrincipal = this.replicaSessionPrincipal ?? (leavingAddress ? pkhDid2(leavingAddress, leavingChainId) : void 0);
4171
4224
  const leavingAuth = this.auth;
4225
+ const leavingContext = this._serviceContext;
4226
+ let denied;
4172
4227
  try {
4173
4228
  this.teardownSession();
4174
4229
  } finally {
4230
+ const denial = leavingPrincipal ? this.denyReplicaGate({ reason: "sign_out", principal: leavingPrincipal }, leavingContext) : Promise.resolve(void 0);
4175
4231
  try {
4176
- await this.accountRegistryTail;
4177
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
4232
+ denied = await denial;
4178
4233
  } finally {
4179
- if (leavingAddress) {
4180
- await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
4234
+ try {
4235
+ await this.accountRegistryTail;
4236
+ await this.quiesceReplicaRuntime();
4237
+ } finally {
4238
+ if (leavingAddress) {
4239
+ await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
4240
+ }
4181
4241
  }
4182
4242
  }
4183
4243
  }
4244
+ if (denied?.status === "unguarded") {
4245
+ throw new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`);
4246
+ }
4247
+ }
4248
+ /**
4249
+ * Step 1 of replica sign-out. Never rejects: failures come back as `unguarded` so the session
4250
+ * is still cleared. Returns `undefined` when replication is off.
4251
+ */
4252
+ async denyReplicaGate(input, context = this._serviceContext) {
4253
+ const replication = this.config.replication;
4254
+ if (!replication?.enabled) return void 0;
4255
+ try {
4256
+ const lifecycle = replication.storage.lifecycle;
4257
+ if (lifecycle?.kind !== (replication.storage.kind === "indexeddb" ? "indexeddb" : "file")) {
4258
+ return { status: "unguarded", code: "gate_unavailable" };
4259
+ }
4260
+ const result = await lifecycle.deny(input);
4261
+ if (result.status === "denied" || result.status === "deleted" || result.status === "marker") {
4262
+ try {
4263
+ (await getNodeReplicationLoaders().authority()).reportReplicaDenial(
4264
+ result,
4265
+ input.reason,
4266
+ replication.onEvent,
4267
+ context
4268
+ );
4269
+ } catch {
4270
+ }
4271
+ }
4272
+ return result;
4273
+ } catch (error) {
4274
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "gate_unavailable";
4275
+ return { status: "unguarded", code };
4276
+ }
4184
4277
  }
4185
4278
  /**
4186
4279
  * Synchronously drop every piece of in-memory session authority and
@@ -4199,15 +4292,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4199
4292
  this._activeServiceSession = void 0;
4200
4293
  oldGraph.retire();
4201
4294
  oldCore?.retireServices();
4202
- oldAuth?.resetSession(
4203
- this.sessionManager,
4204
- this.sessionKeyId,
4205
- this.explicitHost ? [this.explicitHost] : void 0
4206
- );
4207
4295
  this.config.host = this.explicitHost ?? DEFAULT_HOST;
4208
4296
  this._chainId = 1;
4209
4297
  this._restoredNodeFeatures = void 0;
4210
- this._activeServiceSession = void 0;
4211
4298
  this._serviceContext = void 0;
4212
4299
  this._kv = void 0;
4213
4300
  this._sql = void 0;
@@ -4232,6 +4319,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4232
4319
  this._bootstrapStatus = { skipped: false };
4233
4320
  this.replicationDelegateSession = void 0;
4234
4321
  this.replicationScopeError = void 0;
4322
+ this.replicaSessionPrincipal = void 0;
4323
+ this.replicaSessionStamp = void 0;
4324
+ this.replicaRestoreGate = void 0;
4235
4325
  const sessionKey = createDefaultSessionKey(this.wasmBindings);
4236
4326
  this.sessionManager = sessionKey.manager;
4237
4327
  this.sessionKeyId = sessionKey.keyId;
@@ -4241,6 +4331,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4241
4331
  sessionKey.keyId,
4242
4332
  this.explicitHost ? [this.explicitHost] : void 0
4243
4333
  );
4334
+ this.replicaSessionPrincipal = void 0;
4335
+ this.replicaSessionStamp = void 0;
4336
+ this.replicaRestoreGate = void 0;
4244
4337
  this._serviceGraph = this.createServiceGraphLifetime();
4245
4338
  this.auth = oldAuth;
4246
4339
  this.tc = this.auth ? new TinyCloud(this.auth, {
@@ -4270,11 +4363,25 @@ var _TinyCloudNode = class _TinyCloudNode {
4270
4363
  "Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
4271
4364
  );
4272
4365
  }
4366
+ await this.signInWith(() => this.tc.signIn(options));
4367
+ }
4368
+ /**
4369
+ * Fresh OpenKey sign-in. The callback receives the SDK's original SIWE and
4370
+ * session JWK before approval; the signed result is verified against those
4371
+ * bytes before the network session or replica stamp is committed.
4372
+ */
4373
+ async signInWithOpenKey(authorizeFn, options) {
4374
+ if (!this.signer || !this.tc || !this.auth) {
4375
+ throw new Error("Cannot signInWithOpenKey() in session-only mode. Provide a signer in config.");
4376
+ }
4377
+ await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
4378
+ }
4379
+ async signInWith(performSignIn) {
4273
4380
  const epoch = this.sessionEpoch;
4274
4381
  const attempt = ++this.latestSessionAttempt;
4275
4382
  await this.signOutInFlight;
4276
4383
  try {
4277
- await this.establishSignIn(epoch, options);
4384
+ await this.establishSignIn(epoch, performSignIn);
4278
4385
  } catch (error) {
4279
4386
  if (epoch !== this.sessionEpoch && attempt === this.latestSessionAttempt) {
4280
4387
  await this.endSession();
@@ -4282,7 +4389,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4282
4389
  throw error;
4283
4390
  }
4284
4391
  }
4285
- async establishSignIn(epoch, options) {
4392
+ async establishSignIn(epoch, performSignIn) {
4286
4393
  if (!this.signer || !this.tc) {
4287
4394
  throw new Error("Not signed in. Call signIn() first.");
4288
4395
  }
@@ -4292,6 +4399,12 @@ var _TinyCloudNode = class _TinyCloudNode {
4292
4399
  const address = canonicalizeAddress2(await this.signer.getAddress());
4293
4400
  const chainId = await this.signer.getChainId();
4294
4401
  this.assertSessionEpoch(epoch, "Sign-in");
4402
+ const nextPrincipal = pkhDid2(address, chainId);
4403
+ const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? pkhDid2(this.address, this._chainId) : void 0);
4404
+ if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
4405
+ const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
4406
+ this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
4407
+ }
4295
4408
  if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
4296
4409
  this._address = address;
4297
4410
  this._chainId = chainId;
@@ -4309,7 +4422,20 @@ var _TinyCloudNode = class _TinyCloudNode {
4309
4422
  this.runtimePermissionGrants = [];
4310
4423
  this.confirmedHostedSpaceIds.clear();
4311
4424
  this.hostedOwnedSpaceIds.clear();
4312
- await this.tc.signIn(options);
4425
+ const signInAttempt = this.latestSessionAttempt;
4426
+ const previousReplicaGate = this.replicaRestoreGate;
4427
+ const previousReplicaStamp = this.replicaSessionStamp;
4428
+ this.replicaRestoreGate = void 0;
4429
+ this.replicaSessionStamp = void 0;
4430
+ try {
4431
+ await performSignIn();
4432
+ } catch (error) {
4433
+ if (epoch === this.sessionEpoch && signInAttempt === this.latestSessionAttempt) {
4434
+ this.replicaRestoreGate = previousReplicaGate;
4435
+ this.replicaSessionStamp = previousReplicaStamp;
4436
+ }
4437
+ throw error;
4438
+ }
4313
4439
  this.assertSessionEpoch(epoch, "Sign-in");
4314
4440
  this.cancelOwnedSpaceHosts();
4315
4441
  this.syncResolvedHostFromAuth();
@@ -4317,8 +4443,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4317
4443
  this.hostedOwnedSpaceIds.add(spaceId);
4318
4444
  }
4319
4445
  this.replicationDelegateSession = void 0;
4446
+ this.replicaSessionPrincipal = pkhDid2(address, chainId);
4320
4447
  this.replicationScopeError = void 0;
4321
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
4448
+ await this.quiesceReplicaRuntime();
4322
4449
  this.assertSessionEpoch(epoch, "Sign-in");
4323
4450
  const oldGraph = this._serviceGraph;
4324
4451
  this._serviceGraph = this.createServiceGraphLifetime();
@@ -4905,6 +5032,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4905
5032
  if (this.ownStorageFull) {
4906
5033
  return;
4907
5034
  }
5035
+ if (!this.currentSessionCoversAccountRegistry()) {
5036
+ return;
5037
+ }
4908
5038
  const controller = new AbortController();
4909
5039
  const deadline = setTimeout(
4910
5040
  () => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
@@ -5019,6 +5149,27 @@ var _TinyCloudNode = class _TinyCloudNode {
5019
5149
  (operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
5020
5150
  );
5021
5151
  }
5152
+ /**
5153
+ * Whether the current primary session's recap covers the account index read
5154
+ * (`tinycloud.sql/read` on the owned account space's `account` database),
5155
+ * the first call of every registry sync. As in
5156
+ * {@link currentSessionCanListSpaces}, a session with no parseable recap
5157
+ * keeps today's behavior.
5158
+ */
5159
+ currentSessionCoversAccountRegistry() {
5160
+ const session = this.currentTinyCloudSession();
5161
+ const operations = session ? this.recapOperationsFromSession(session) : [];
5162
+ if (operations.length === 0) {
5163
+ return true;
5164
+ }
5165
+ const indexRead = {
5166
+ spaceId: this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2),
5167
+ service: "sql",
5168
+ path: "account",
5169
+ action: "tinycloud.sql/read"
5170
+ };
5171
+ return operations.some((granted) => this.operationCovers(granted, indexRead));
5172
+ }
5022
5173
  async withAccountRegistryRetry(task, signal) {
5023
5174
  const delays = [250, 1e3, 3e3];
5024
5175
  const stopped = (detail) => {
@@ -5485,11 +5636,12 @@ var _TinyCloudNode = class _TinyCloudNode {
5485
5636
  );
5486
5637
  const stagedHost = resolvedHost ?? this.config.host;
5487
5638
  let restoredNodeFeatures;
5639
+ let replicaProtocolMismatch = false;
5488
5640
  if (this.config.replication?.enabled) {
5489
5641
  try {
5490
5642
  restoredNodeFeatures = (await checkNodeInfo2(stagedHost, this.wasmBindings.protocolVersion())).features;
5491
5643
  } catch (error) {
5492
- if (!(error instanceof VersionCheckError)) throw error;
5644
+ replicaProtocolMismatch = error instanceof ProtocolMismatchError;
5493
5645
  }
5494
5646
  }
5495
5647
  const stagedAddress = restoredAddress;
@@ -5497,28 +5649,55 @@ var _TinyCloudNode = class _TinyCloudNode {
5497
5649
  let replicationPrincipal;
5498
5650
  let compactReplicationSession;
5499
5651
  let replicationScopeError;
5652
+ let replicationScopeUnavailable = false;
5500
5653
  if (this.config.replication?.enabled) {
5501
5654
  if (stagedAddress !== void 0) {
5502
5655
  replicationPrincipal = pkhDid2(stagedAddress, stagedChainId);
5503
5656
  } else {
5504
- const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
5505
- const signedScope = replicationScopeFromSignedSession({
5506
- delegationHeader: sessionData.delegationHeader,
5507
- delegationCid: sessionData.delegationCid,
5508
- verificationMethod: restoredVerificationMethod,
5509
- persistedSpace: sessionData.spaceId
5510
- });
5511
- if (!signedScope) {
5512
- replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
5513
- } else {
5514
- replicationPrincipal = signedScope.principal;
5515
- compactReplicationSession = {
5657
+ try {
5658
+ const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
5659
+ const signedScope = replicationScopeFromSignedSession({
5516
5660
  delegationHeader: sessionData.delegationHeader,
5517
5661
  delegationCid: sessionData.delegationCid,
5518
- spaceId: signedScope.space,
5519
5662
  verificationMethod: restoredVerificationMethod,
5520
- jwk: stagedJwk
5521
- };
5663
+ persistedSpace: sessionData.spaceId
5664
+ });
5665
+ if (!signedScope) {
5666
+ replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
5667
+ } else {
5668
+ replicationPrincipal = signedScope.principal;
5669
+ compactReplicationSession = {
5670
+ delegationHeader: sessionData.delegationHeader,
5671
+ delegationCid: sessionData.delegationCid,
5672
+ spaceId: signedScope.space,
5673
+ verificationMethod: restoredVerificationMethod,
5674
+ jwk: stagedJwk
5675
+ };
5676
+ }
5677
+ } catch {
5678
+ replicationScopeUnavailable = true;
5679
+ replicationScopeError = "Replica scope could not be verified for this network session.";
5680
+ }
5681
+ }
5682
+ }
5683
+ let replicaRestoreGate;
5684
+ let restoredReplicaStamp;
5685
+ const replication = this.config.replication;
5686
+ if (replication?.enabled) {
5687
+ if (replicaProtocolMismatch) {
5688
+ replicaRestoreGate = "gate_unavailable";
5689
+ } else if (replicationPrincipal === void 0) {
5690
+ replicaRestoreGate = replicationScopeUnavailable ? "gate_unavailable" : "principal_mismatch";
5691
+ } else {
5692
+ try {
5693
+ const { restoreReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
5694
+ restoredReplicaStamp = await restoreReplicaSessionStamp(
5695
+ replication.storage,
5696
+ replicationPrincipal,
5697
+ sessionData.replicaStamp ?? void 0
5698
+ );
5699
+ } catch (error) {
5700
+ replicaRestoreGate = replicaRestoreRefusal(error);
5522
5701
  }
5523
5702
  }
5524
5703
  }
@@ -5611,7 +5790,10 @@ var _TinyCloudNode = class _TinyCloudNode {
5611
5790
  }
5612
5791
  this.replicationDelegateSession = compactReplicationSession;
5613
5792
  this.replicationScopeError = replicationScopeError;
5614
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
5793
+ this.replicaRestoreGate = replicaRestoreGate;
5794
+ this.replicaSessionPrincipal = replicationPrincipal;
5795
+ this.replicaSessionStamp = restoredReplicaStamp;
5796
+ await this.quiesceReplicaRuntime();
5615
5797
  if (epoch !== this.sessionEpoch) {
5616
5798
  oldGraph.retire();
5617
5799
  oldCore?.retireServices?.();
@@ -5626,7 +5808,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5626
5808
  detail: replicationScopeError
5627
5809
  });
5628
5810
  }
5629
- await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
5811
+ await this.bindReplicaOrGate(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
5630
5812
  oldCore?.retireServices?.();
5631
5813
  if (epoch !== this.sessionEpoch) {
5632
5814
  if (attempt === this.latestSessionAttempt) await this.endSession();
@@ -5759,6 +5941,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5759
5941
  session: input.serviceSession,
5760
5942
  invoke: graph.invoke,
5761
5943
  invokeAny: graph.invokeAny,
5944
+ signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
5762
5945
  fetch: graph.fetch
5763
5946
  });
5764
5947
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -6102,6 +6285,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6102
6285
  signer: this.signer,
6103
6286
  signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
6104
6287
  wasmBindings: this.wasmBindings,
6288
+ ...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
6105
6289
  sessionManager: this.sessionManager,
6106
6290
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
6107
6291
  domain: this.siweDomain,
@@ -6160,6 +6344,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6160
6344
  signer: this.signer,
6161
6345
  signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
6162
6346
  wasmBindings: this.wasmBindings,
6347
+ ...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
6163
6348
  sessionManager: this.sessionManager,
6164
6349
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
6165
6350
  domain: this.siweDomain,
@@ -6240,7 +6425,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6240
6425
  jwk: session.jwk
6241
6426
  };
6242
6427
  this._serviceContext.setSession(serviceSession);
6243
- await this.bindReplicationRuntime(this._serviceContext, serviceSession, this._kv);
6428
+ await this.bindReplicaOrGate(this._serviceContext, serviceSession, this._kv);
6244
6429
  this.tc.serviceContext.setSession(serviceSession);
6245
6430
  this._vault = this.createVaultService(session.spaceId, this._kv);
6246
6431
  this._vault.initialize(this._serviceContext);
@@ -6634,6 +6819,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6634
6819
  session: serviceSession,
6635
6820
  invoke: graph.invoke,
6636
6821
  invokeAny: graph.invokeAny,
6822
+ signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
6637
6823
  fetch: graph.fetch
6638
6824
  });
6639
6825
  this._spaceService = new SpaceService({
@@ -6769,11 +6955,12 @@ var _TinyCloudNode = class _TinyCloudNode {
6769
6955
  * Both SharingService (single-entry) and
6770
6956
  * {@link TinyCloudNode.delegateTo} (multi-entry) drive this through
6771
6957
  * the same code path so there's exactly one place that touches the
6772
- * WASM boundary.
6958
+ * WASM boundary. `terminal` adds the node's terminal marker, which the
6959
+ * WASM builder cannot emit.
6773
6960
  *
6774
6961
  * @internal
6775
6962
  */
6776
- createDelegationWrapper(params) {
6963
+ createDelegationWrapper(params, terminal = false) {
6777
6964
  const wasmSession = {
6778
6965
  delegationHeader: params.session.delegationHeader,
6779
6966
  delegationCid: params.session.delegationCid,
@@ -6793,8 +6980,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6793
6980
  params.notBeforeSecs
6794
6981
  );
6795
6982
  return {
6796
- delegation: result.delegation,
6797
- cid: result.cid,
6983
+ ...terminal ? markDelegationTerminal(result.delegation, params.session.jwk) : { delegation: result.delegation, cid: result.cid },
6798
6984
  // Rust serde `rename_all = "camelCase"` emits `delegateDid`
6799
6985
  // (lowercase d); the TypeScript interface uses `delegateDID`
6800
6986
  // (historical, matches Delegation.delegateDID). Normalize here.
@@ -7926,7 +8112,8 @@ var _TinyCloudNode = class _TinyCloudNode {
7926
8112
  return this.delegationManager.create(params);
7927
8113
  }
7928
8114
  /**
7929
- * Revoke a delegation using the v2 DelegationManager.
8115
+ * Revoke a delegation with the owner wallet when available. Session-only
8116
+ * clients continue to use their delegated revocation authority.
7930
8117
  *
7931
8118
  * @param cid - The CID of the delegation to revoke
7932
8119
  * @returns Result indicating success or failure
@@ -8042,6 +8229,9 @@ var _TinyCloudNode = class _TinyCloudNode {
8042
8229
  effectiveExpiration = sessionExpiry;
8043
8230
  }
8044
8231
  if (options?.forceWalletSign) {
8232
+ if (options.disableSubDelegation) {
8233
+ throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
8234
+ }
8045
8235
  if (expandedEntries.length > 1) {
8046
8236
  throw new Error(
8047
8237
  "delegateTo with forceWalletSign=true supports at most one PermissionEntry. Multi-entry requests must go through the session-key UCAN path (drop forceWalletSign) or the legacy createDelegation method."
@@ -8077,7 +8267,8 @@ var _TinyCloudNode = class _TinyCloudNode {
8077
8267
  plan.effectiveExpiration,
8078
8268
  plan.grant,
8079
8269
  plan.operations,
8080
- onPrepared
8270
+ onPrepared,
8271
+ options?.disableSubDelegation === true
8081
8272
  );
8082
8273
  return { delegation: delegation2, prompted: false };
8083
8274
  }
@@ -8086,7 +8277,8 @@ var _TinyCloudNode = class _TinyCloudNode {
8086
8277
  expandedEntries,
8087
8278
  effectiveExpiration,
8088
8279
  session,
8089
- onPrepared
8280
+ onPrepared,
8281
+ options?.disableSubDelegation === true
8090
8282
  );
8091
8283
  return { delegation, prompted: false };
8092
8284
  }
@@ -8201,34 +8393,67 @@ var _TinyCloudNode = class _TinyCloudNode {
8201
8393
  return auth.replicationSignInEntries();
8202
8394
  }
8203
8395
  async replicationAuthority() {
8204
- const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
8396
+ const { createReplicationAuthority, mintNodeReplicationGrant, verifiedOwnerConsentAnchor } = await getNodeReplicationLoaders().authority();
8205
8397
  return createReplicationAuthority({
8206
8398
  replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
8399
+ verifiedConsentAnchor: (session) => {
8400
+ const current = this.currentTinyCloudSession();
8401
+ return session === current && current !== void 0 ? verifiedOwnerConsentAnchor(current, this.wasmBindings, exactVerifiedRecap) : null;
8402
+ },
8207
8403
  siweExpiration: (siwe) => extractSiweExpiration(siwe),
8208
8404
  planDelegation: (entries, options) => this.planDelegation(entries, options),
8209
- mintDelegation: async (deviceDid, entries) => {
8210
- const result = await this.delegateTo(principalDid(deviceDid), entries);
8211
- return {
8212
- ucan: result.delegation.delegationHeader.Authorization,
8213
- expiresAt: result.delegation.expiry.getTime()
8214
- };
8215
- }
8405
+ mintDelegation: (deviceDid, entries, options) => mintNodeReplicationGrant(
8406
+ deviceDid,
8407
+ entries,
8408
+ options,
8409
+ options?.renewalOnly ? this.currentTinyCloudSession() : void 0,
8410
+ this
8411
+ )
8216
8412
  });
8217
8413
  }
8218
- async bindReplicationRuntime(context, session, kv, principal) {
8414
+ async quiesceReplicaRuntime() {
8219
8415
  if (!this.replicationRuntime) return;
8220
- const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
8221
- if (identityPrincipal === void 0) return;
8222
- const runtime = await this.replicationRuntime;
8223
- const authority = await this.replicationAuthority();
8224
- await runtime.bind({
8225
- context,
8226
- session,
8227
- principal: identityPrincipal,
8228
- authority,
8229
- nodeFeatures: this.replicationNodeFeatures,
8230
- primaryKV: [{ space: session.spaceId, kv }]
8231
- });
8416
+ try {
8417
+ await (await this.replicationRuntime).unbind();
8418
+ } catch {
8419
+ this.replicaRestoreGate = "gate_unavailable";
8420
+ }
8421
+ }
8422
+ async bindReplicaOrGate(context, session, kv, principal) {
8423
+ if (!this.replicationRuntime) return;
8424
+ const gate = this.replicaRestoreGate;
8425
+ const epoch = this.sessionEpoch;
8426
+ const attempt = this.latestSessionAttempt;
8427
+ try {
8428
+ const runtime = await this.replicationRuntime;
8429
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context || gate !== this.replicaRestoreGate) return;
8430
+ if (gate) {
8431
+ await runtime.bindGatedNetworkOnly({
8432
+ context,
8433
+ session,
8434
+ gate,
8435
+ primaryKV: [{ space: session.spaceId, kv }]
8436
+ }, getNodeReplicationLoaders().gatedNetwork);
8437
+ return;
8438
+ }
8439
+ const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
8440
+ if (identityPrincipal === void 0) return;
8441
+ const authority = await this.replicationAuthority();
8442
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
8443
+ await runtime.bind({
8444
+ context,
8445
+ session,
8446
+ principal: identityPrincipal,
8447
+ stamp: this.replicaSessionStamp,
8448
+ authority,
8449
+ nodeFeatures: this.replicationNodeFeatures,
8450
+ primaryKV: [{ space: session.spaceId, kv }]
8451
+ });
8452
+ } catch {
8453
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
8454
+ this.replicaRestoreGate = "gate_unavailable";
8455
+ await this.quiesceReplicaRuntime();
8456
+ }
8232
8457
  }
8233
8458
  /** Stable control facade for this node's optional replication runtime. */
8234
8459
  get replication() {
@@ -8284,7 +8509,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8284
8509
  *
8285
8510
  * @internal
8286
8511
  */
8287
- async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared) {
8512
+ async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared, disableSubDelegation = false) {
8288
8513
  if (entries.length === 0) {
8289
8514
  throw new Error(
8290
8515
  "createDelegationViaWasmPath requires a non-empty entries array"
@@ -8344,7 +8569,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8344
8569
  spaceId,
8345
8570
  abilities,
8346
8571
  expirationSecs
8347
- });
8572
+ }, disableSubDelegation);
8348
8573
  const primary = result.resources[0];
8349
8574
  const delegationHeader = { Authorization: result.delegation };
8350
8575
  const delegation = {
@@ -8354,7 +8579,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8354
8579
  path: primary.path,
8355
8580
  actions: primary.actions,
8356
8581
  resources: result.resources,
8357
- disableSubDelegation: false,
8582
+ disableSubDelegation,
8358
8583
  expiry: result.expiry,
8359
8584
  delegateDID: did,
8360
8585
  ownerAddress: session.address,
@@ -8376,7 +8601,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8376
8601
  }
8377
8602
  return delegation;
8378
8603
  }
8379
- async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared) {
8604
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared, disableSubDelegation = false) {
8380
8605
  this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
8381
8606
  this.assertDelegationCaveatsPreservable(entries);
8382
8607
  const result = this.createDelegationWrapper({
@@ -8385,7 +8610,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8385
8610
  spaceId: grant.session.spaceId,
8386
8611
  abilities: this.permissionsToAbilities(entries),
8387
8612
  expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
8388
- });
8613
+ }, disableSubDelegation);
8389
8614
  const primary = result.resources[0];
8390
8615
  const delegationHeader = { Authorization: result.delegation };
8391
8616
  const targetHost = grant.delegation.host ?? this.config.host;
@@ -8396,7 +8621,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8396
8621
  path: primary.path,
8397
8622
  actions: primary.actions,
8398
8623
  resources: result.resources,
8399
- disableSubDelegation: false,
8624
+ disableSubDelegation,
8400
8625
  expiry: result.expiry,
8401
8626
  delegateDID: did,
8402
8627
  ownerAddress: grant.delegation.ownerAddress,
@@ -8980,11 +9205,10 @@ var _TinyCloudNode = class _TinyCloudNode {
8980
9205
  params.spaceIdOverride
8981
9206
  );
8982
9207
  try {
8983
- const result = await this.delegateTo(
8984
- resolvedDelegateDID,
8985
- entries,
8986
- params.expiryMs !== void 0 ? { expiry: params.expiryMs } : void 0
8987
- );
9208
+ const result = await this.delegateTo(resolvedDelegateDID, entries, {
9209
+ ...params.expiryMs !== void 0 ? { expiry: params.expiryMs } : {},
9210
+ disableSubDelegation: params.disableSubDelegation
9211
+ });
8988
9212
  return result.delegation;
8989
9213
  } catch (err2) {
8990
9214
  if (err2 instanceof PermissionNotInManifestError) {
@@ -9005,6 +9229,9 @@ var _TinyCloudNode = class _TinyCloudNode {
9005
9229
  * @internal
9006
9230
  */
9007
9231
  async createDelegationWalletPath(params) {
9232
+ if (params.disableSubDelegation) {
9233
+ throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
9234
+ }
9008
9235
  if (!this.signer) {
9009
9236
  throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
9010
9237
  }
@@ -9060,7 +9287,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9060
9287
  spaceId: params.spaceIdOverride ?? session.spaceId,
9061
9288
  path: params.path,
9062
9289
  actions: params.actions,
9063
- disableSubDelegation: params.disableSubDelegation ?? false,
9290
+ disableSubDelegation: false,
9064
9291
  expiry: expirationTime,
9065
9292
  delegateDID: params.delegateDID,
9066
9293
  ownerAddress: session.address,
@@ -9103,7 +9330,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9103
9330
  spaceId: publicSpaceId,
9104
9331
  path: "",
9105
9332
  actions: [KV3.GET, KV3.PUT, KV3.METADATA],
9106
- disableSubDelegation: params.disableSubDelegation ?? false,
9333
+ disableSubDelegation: false,
9107
9334
  expiry: expirationTime,
9108
9335
  delegateDID: params.delegateDID,
9109
9336
  ownerAddress: session.address,
@@ -9270,6 +9497,9 @@ var _TinyCloudNode = class _TinyCloudNode {
9270
9497
  if (parentDelegation.disableSubDelegation) {
9271
9498
  throw new Error("Parent delegation does not allow sub-delegation");
9272
9499
  }
9500
+ if (params.disableSubDelegation) {
9501
+ throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
9502
+ }
9273
9503
  const parentResources = parentDelegation.resources ?? [{
9274
9504
  service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
9275
9505
  space: parentDelegation.spaceId,
@@ -9342,7 +9572,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9342
9572
  spaceId: parentDelegation.spaceId,
9343
9573
  path: requestedResources[0].path,
9344
9574
  actions: requestedResources[0].actions,
9345
- disableSubDelegation: params.disableSubDelegation ?? false,
9575
+ disableSubDelegation: false,
9346
9576
  expiry: actualExpiry,
9347
9577
  delegateDID: params.delegateDID,
9348
9578
  ownerAddress: parentDelegation.ownerAddress,
@@ -9436,8 +9666,8 @@ import {
9436
9666
  Space
9437
9667
  } from "@tinycloud/sdk-core";
9438
9668
  import {
9439
- ProtocolMismatchError,
9440
- VersionCheckError as VersionCheckError2,
9669
+ ProtocolMismatchError as ProtocolMismatchError2,
9670
+ VersionCheckError,
9441
9671
  UnsupportedFeatureError as UnsupportedFeatureError2,
9442
9672
  checkNodeInfo as checkNodeInfo3
9443
9673
  } from "@tinycloud/sdk-core";
@@ -9470,7 +9700,7 @@ export {
9470
9700
  NodeUserAuthorization,
9471
9701
  PermissionNotInManifestError2 as PermissionNotInManifestError,
9472
9702
  PrefixedKVService,
9473
- ProtocolMismatchError,
9703
+ ProtocolMismatchError2 as ProtocolMismatchError,
9474
9704
  SECRET_NAME_RE,
9475
9705
  SQLAction,
9476
9706
  SQLService3 as SQLService,
@@ -9490,7 +9720,7 @@ export {
9490
9720
  VAULT_PERMISSION_SERVICE,
9491
9721
  VaultHeaders,
9492
9722
  VaultPublicSpaceKVActions,
9493
- VersionCheckError2 as VersionCheckError,
9723
+ VersionCheckError,
9494
9724
  WasmKeyProvider,
9495
9725
  activateCompactRuntimeDelegation,
9496
9726
  addressStorageKey,
@@ -9519,6 +9749,7 @@ export {
9519
9749
  expandActionShortNames,
9520
9750
  expandPermissionEntries2 as expandPermissionEntries,
9521
9751
  expandPermissionEntry,
9752
+ getNodeReplicationLoaders,
9522
9753
  getTinyCloudDebugLogs,
9523
9754
  installTinyCloudDebugGlobals,
9524
9755
  isCapabilitySubset3 as isCapabilitySubset,
@@ -9532,7 +9763,7 @@ export {
9532
9763
  parseSecretCatalogKey,
9533
9764
  parseSpaceUri2 as parseSpaceUri,
9534
9765
  pkhDid3 as pkhDid,
9535
- principalDid2 as principalDid,
9766
+ principalDid,
9536
9767
  principalDidEquals4 as principalDidEquals,
9537
9768
  registerReplicationLoaders,
9538
9769
  resolveManifest2 as resolveManifest,