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

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 +401 -168
  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 +408 -176
  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 +2735 -545
  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 +2730 -545
  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.cjs CHANGED
@@ -106,6 +106,7 @@ __export(core_exports, {
106
106
  expandActionShortNames: () => import_sdk_core13.expandActionShortNames,
107
107
  expandPermissionEntries: () => import_sdk_core13.expandPermissionEntries,
108
108
  expandPermissionEntry: () => import_sdk_core13.expandPermissionEntry,
109
+ getNodeReplicationLoaders: () => getNodeReplicationLoaders,
109
110
  getTinyCloudDebugLogs: () => import_sdk_core12.getTinyCloudDebugLogs,
110
111
  installTinyCloudDebugGlobals: () => import_sdk_core12.installTinyCloudDebugGlobals,
111
112
  isCapabilitySubset: () => import_sdk_core13.isCapabilitySubset,
@@ -526,6 +527,7 @@ var NodeUserAuthorization = class {
526
527
  this.siweConfig = config.siweConfig;
527
528
  this.includeAccountRegistryPermissions = config.includeAccountRegistryPermissions ?? true;
528
529
  this.replication = config.replication?.enabled === true ? config.replication : void 0;
530
+ this.establishReplicaStamp = config.establishReplicaStamp;
529
531
  this._manifest = config.manifest;
530
532
  this._capabilityRequest = config.capabilityRequest;
531
533
  this.sessionManager = config.sessionManager ?? this.wasm.createSessionManager();
@@ -1295,6 +1297,27 @@ var NodeUserAuthorization = class {
1295
1297
  signature
1296
1298
  };
1297
1299
  }
1300
+ persistedData(session, expiresAt, createdAt, replicaStamp) {
1301
+ return {
1302
+ address: session.address,
1303
+ chainId: session.chainId,
1304
+ sessionKey: JSON.stringify(session.jwk),
1305
+ siwe: session.siwe,
1306
+ signature: session.signature,
1307
+ tinycloudSession: {
1308
+ delegationHeader: session.delegationHeader,
1309
+ delegationCid: session.delegationCid,
1310
+ spaceId: session.spaceId,
1311
+ spaces: session.spaces,
1312
+ verificationMethod: session.verificationMethod
1313
+ },
1314
+ expiresAt,
1315
+ createdAt,
1316
+ version: "1.0",
1317
+ tinycloudHosts: this.tinycloudHosts,
1318
+ ...replicaStamp ? { replicaStamp } : {}
1319
+ };
1320
+ }
1298
1321
  /**
1299
1322
  * Sign in and create a new session.
1300
1323
  *
@@ -1374,24 +1397,13 @@ var NodeUserAuthorization = class {
1374
1397
  siwe: prepared.siwe,
1375
1398
  signature
1376
1399
  };
1377
- const persistedData = {
1378
- address,
1379
- chainId,
1380
- sessionKey: JSON.stringify(jwk),
1381
- siwe: prepared.siwe,
1382
- signature,
1383
- tinycloudSession: {
1384
- delegationHeader: session.delegationHeader,
1385
- delegationCid: session.delegationCid,
1386
- spaceId,
1387
- spaces: spacesMetadata,
1388
- verificationMethod: this.sessionManager.getDID(keyId)
1389
- },
1390
- expiresAt: expirationTime.toISOString(),
1391
- createdAt: now.toISOString(),
1392
- version: "1.0",
1393
- tinycloudHosts: this.tinycloudHosts
1394
- };
1400
+ const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
1401
+ const persistedData = this.persistedData(
1402
+ tinyCloudSession,
1403
+ expirationTime.toISOString(),
1404
+ now.toISOString(),
1405
+ replicaStamp
1406
+ );
1395
1407
  await this.enqueuePersistence(async () => {
1396
1408
  await this.sessionStorage.save(address, persistedData);
1397
1409
  if (generation !== this.sessionGeneration) {
@@ -1545,28 +1557,12 @@ var NodeUserAuthorization = class {
1545
1557
  siwe: prepared.siwe,
1546
1558
  signature
1547
1559
  };
1548
- const expirationMatch = prepared.siwe.match(/Expiration Time: (.+)/);
1549
- const issuedAtMatch = prepared.siwe.match(/Issued At: (.+)/);
1560
+ const expirationMatch = prepared.siwe?.match(/Expiration Time: (.+)/);
1561
+ const issuedAtMatch = prepared.siwe?.match(/Issued At: (.+)/);
1550
1562
  const expiresAt = expirationMatch?.[1] ?? new Date(Date.now() + this.sessionExpirationMs).toISOString();
1551
1563
  const createdAt = issuedAtMatch?.[1] ?? (/* @__PURE__ */ new Date()).toISOString();
1552
- const persistedData = {
1553
- address,
1554
- chainId,
1555
- sessionKey: JSON.stringify(jwk),
1556
- siwe: prepared.siwe,
1557
- signature,
1558
- tinycloudSession: {
1559
- delegationHeader: session.delegationHeader,
1560
- delegationCid: session.delegationCid,
1561
- spaceId: prepared.spaceId,
1562
- spaces: spacesMetadata,
1563
- verificationMethod: this.sessionManager.getDID(keyId)
1564
- },
1565
- expiresAt,
1566
- createdAt,
1567
- version: "1.0",
1568
- tinycloudHosts: this.tinycloudHosts
1569
- };
1564
+ const replicaStamp = await this.establishReplicaStamp?.(tinyCloudSession);
1565
+ const persistedData = this.persistedData(tinyCloudSession, expiresAt, createdAt, replicaStamp);
1570
1566
  await this.sessionStorage.save(address, persistedData);
1571
1567
  this._session = clientSession;
1572
1568
  this._tinyCloudSession = tinyCloudSession;
@@ -1629,15 +1625,36 @@ var NodeUserAuthorization = class {
1629
1625
  const { prepared, keyId, jwk } = await this.prepareSessionForSigning();
1630
1626
  const presentation = this.buildPresentationEnvelope(options?.reason);
1631
1627
  const hostHint = this.primaryTinyCloudHost;
1632
- const result = await authorizeFn({
1628
+ const originalRequest = {
1633
1629
  protocolVersion: 1,
1634
1630
  siwe: prepared.siwe,
1635
1631
  jwk,
1632
+ verificationMethod: prepared.verificationMethod,
1636
1633
  host: hostHint,
1637
1634
  keyId: options?.openkeyKeyId,
1638
1635
  presentation
1639
- });
1640
- return this.signInWithOpenKeyResult(result, prepared, keyId, jwk);
1636
+ };
1637
+ let result;
1638
+ let signed;
1639
+ let signature;
1640
+ for (let attempt = 0; ; ) {
1641
+ try {
1642
+ result ?? (result = await authorizeFn(originalRequest));
1643
+ signed = await this.verifyOpenKeyResult(result, prepared);
1644
+ signature = result.signature;
1645
+ break;
1646
+ } catch (error) {
1647
+ if (!options?.retryResult) throw error;
1648
+ const next = await options.retryResult(
1649
+ error instanceof Error ? error : new Error(String(error)),
1650
+ originalRequest,
1651
+ ++attempt
1652
+ );
1653
+ if (next === null) throw error;
1654
+ result = next;
1655
+ }
1656
+ }
1657
+ return this.signInWithPreparedSession(signed, signature, keyId, jwk);
1641
1658
  }
1642
1659
  /**
1643
1660
  * Build a `CapabilityPresentationEnvelopeV1` from `this._manifest`. The
@@ -1717,6 +1734,11 @@ var NodeUserAuthorization = class {
1717
1734
  * (resource, action) pair in the returned SIWE.
1718
1735
  */
1719
1736
  async signInWithOpenKeyResult(result, prepared, keyId, jwk) {
1737
+ const signed = await this.verifyOpenKeyResult(result, prepared);
1738
+ return this.signInWithPreparedSession(signed, result.signature, keyId, jwk);
1739
+ }
1740
+ /** Validate a candidate without activating a network session, so paste retries reuse one preparation. */
1741
+ async verifyOpenKeyResult(result, prepared) {
1720
1742
  if (result.protocolVersion !== 1) {
1721
1743
  throw new Error(`Unsupported OpenKey protocol version ${result.protocolVersion}`);
1722
1744
  }
@@ -1961,17 +1983,12 @@ var NodeUserAuthorization = class {
1961
1983
  }
1962
1984
  }
1963
1985
  void nonRequiredGrantedPairs;
1964
- return this.signInWithPreparedSession(
1965
- {
1966
- siwe: result.signedMessage,
1967
- jwk: prepared.jwk,
1968
- spaceId: prepared.spaceId,
1969
- verificationMethod: prepared.verificationMethod
1970
- },
1971
- result.signature,
1972
- keyId,
1973
- jwk
1974
- );
1986
+ return {
1987
+ siwe: result.signedMessage,
1988
+ jwk: prepared.jwk,
1989
+ spaceId: prepared.spaceId,
1990
+ verificationMethod: prepared.verificationMethod
1991
+ };
1975
1992
  }
1976
1993
  /**
1977
1994
  * Clear persisted session data.
@@ -2530,41 +2547,6 @@ function deserializeDelegation(data) {
2530
2547
 
2531
2548
  // src/TinyCloudNode.ts
2532
2549
  var import_siwe2 = require("siwe");
2533
-
2534
- // src/replication/config.ts
2535
- var import_prefix2 = require("@tinycloud/sdk-services/kv/replication/prefix");
2536
- function assertValidReplicationConfig(replication) {
2537
- if (replication === void 0 || replication.enabled !== true) return;
2538
- if (replication.storage === void 0 || replication.storage === null) {
2539
- throw new TypeError("replication.enabled requires replication.storage");
2540
- }
2541
- if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
2542
- throw new TypeError("replication.prefixes must be a non-empty array");
2543
- }
2544
- for (const prefix of replication.prefixes) {
2545
- try {
2546
- (0, import_prefix2.assertFolderPrefix)(prefix);
2547
- } catch (error) {
2548
- throw new TypeError(error instanceof Error ? error.message : String(error));
2549
- }
2550
- }
2551
- for (const [index, prefix] of replication.prefixes.entries()) {
2552
- for (const other of replication.prefixes.slice(index + 1)) {
2553
- if ((0, import_prefix2.kvPrefixCovers)(prefix, other) || (0, import_prefix2.kvPrefixCovers)(other, prefix)) {
2554
- throw new TypeError(
2555
- `replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
2556
- );
2557
- }
2558
- }
2559
- if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
2560
- throw new TypeError(
2561
- `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
2562
- );
2563
- }
2564
- }
2565
- }
2566
-
2567
- // src/TinyCloudNode.ts
2568
2550
  var import_sdk_core10 = require("@tinycloud/sdk-core");
2569
2551
  var import_sdk_services2 = require("@tinycloud/sdk-services");
2570
2552
 
@@ -3365,6 +3347,7 @@ function ownerPermissionsToAbilities(permissions) {
3365
3347
  }
3366
3348
  return { abilities, rawAbilities };
3367
3349
  }
3350
+ var TERMINAL_DELEGATION_REQUIRES_SESSION_KEY = "disableSubDelegation requires a session-key delegation";
3368
3351
  function base64UrlEncode(bytes) {
3369
3352
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3370
3353
  let output = "";
@@ -3476,6 +3459,20 @@ function accountIndexWarningDiagnostic(error) {
3476
3459
  }
3477
3460
  return diagnostic;
3478
3461
  }
3462
+ function replicaRestoreRefusal(error) {
3463
+ if (error !== null && typeof error === "object" && "code" in error && error.code === "REPLICA_GATED" && "gate" in error) {
3464
+ switch (error.gate) {
3465
+ case "not_established":
3466
+ case "stamp_stale":
3467
+ case "principal_mismatch":
3468
+ case "purge_pending":
3469
+ case "logout_pending":
3470
+ case "gate_unavailable":
3471
+ return error.gate;
3472
+ }
3473
+ }
3474
+ return "gate_unavailable";
3475
+ }
3479
3476
  var _TinyCloudNode = class _TinyCloudNode {
3480
3477
  /**
3481
3478
  * Create a new TinyCloudNode instance.
@@ -3505,6 +3502,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3505
3502
  this.auth = null;
3506
3503
  this.tc = null;
3507
3504
  this._chainId = 1;
3505
+ /** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
3506
+ this.establishReplicaStamp = async (session) => {
3507
+ const replication = this.config.replication;
3508
+ if (!replication?.enabled) return void 0;
3509
+ const epoch = this.sessionEpoch;
3510
+ const attempt = this.latestSessionAttempt;
3511
+ try {
3512
+ const { verifiedOwnerConsentAnchor, establishReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
3513
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
3514
+ const anchor = verifiedOwnerConsentAnchor(session, this.wasmBindings, exactVerifiedRecap);
3515
+ if (anchor === null) {
3516
+ throw Object.assign(new Error("Replica sign-in requires a verified finite owner SIWE and exact-space KV get+sync."), {
3517
+ code: "REPLICA_GATED",
3518
+ gate: "not_established"
3519
+ });
3520
+ }
3521
+ const stamp = await establishReplicaSessionStamp(
3522
+ replication.storage,
3523
+ (0, import_sdk_core10.pkhDid)(session.address, session.chainId),
3524
+ anchor
3525
+ );
3526
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
3527
+ this.replicaSessionStamp = stamp;
3528
+ return stamp;
3529
+ } catch (error) {
3530
+ if (epoch === this.sessionEpoch && attempt === this.latestSessionAttempt) {
3531
+ this.replicaRestoreGate = replicaRestoreRefusal(error);
3532
+ }
3533
+ return void 0;
3534
+ }
3535
+ };
3508
3536
  this._baseVaults = /* @__PURE__ */ new Map();
3509
3537
  this._baseSecrets = /* @__PURE__ */ new Map();
3510
3538
  this._secrets = /* @__PURE__ */ new Map();
@@ -3629,7 +3657,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3629
3657
  });
3630
3658
  return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
3631
3659
  };
3632
- assertValidReplicationConfig(config.replication);
3660
+ if (config.replication?.enabled) getNodeReplicationLoaders().validateConfig(config.replication);
3633
3661
  this.explicitHost = config.host;
3634
3662
  this.config = {
3635
3663
  ...config,
@@ -3644,6 +3672,16 @@ var _TinyCloudNode = class _TinyCloudNode {
3644
3672
  this.replicationRuntime = runtime;
3645
3673
  this.replicationControl = {
3646
3674
  status: async () => {
3675
+ const gate = this.replicaRestoreGate;
3676
+ if (gate) return this.config.replication.prefixes.map((prefix) => ({
3677
+ prefix,
3678
+ state: "gated",
3679
+ gate,
3680
+ errorCode: "REPLICA_GATED",
3681
+ pending: { inFlight: 0, committed: 0, ambiguous: 0 },
3682
+ pinned: [],
3683
+ lagMs: null
3684
+ }));
3647
3685
  const status = await runtime.then((ready) => ready.control.status());
3648
3686
  const scopeError = this.replicationScopeError;
3649
3687
  if (!scopeError) return status;
@@ -3660,7 +3698,15 @@ var _TinyCloudNode = class _TinyCloudNode {
3660
3698
  lagMs: null
3661
3699
  }));
3662
3700
  },
3663
- sync: async (input) => (await runtime).control.sync(input),
3701
+ sync: async (input) => {
3702
+ if (this.replicaRestoreGate) {
3703
+ throw Object.assign(new Error("Replica restore is gated."), {
3704
+ code: "REPLICA_GATED",
3705
+ gate: this.replicaRestoreGate
3706
+ });
3707
+ }
3708
+ return (await runtime).control.sync(input);
3709
+ },
3664
3710
  purge: (input) => {
3665
3711
  const instance = this.replicationRuntimeInstance;
3666
3712
  return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
@@ -3876,6 +3922,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3876
3922
  signer: this.signer,
3877
3923
  signStrategy: config.signStrategy ?? { type: "auto-sign" },
3878
3924
  wasmBindings: this.wasmBindings,
3925
+ ...config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
3879
3926
  sessionManager: this.sessionManager,
3880
3927
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
3881
3928
  domain: this.siweDomain,
@@ -4073,11 +4120,10 @@ var _TinyCloudNode = class _TinyCloudNode {
4073
4120
  return this.signer === null;
4074
4121
  }
4075
4122
  /**
4076
- * Get the space ID for this user.
4077
- * Available after signIn().
4123
+ * Primary space of the signed-in or restored network session, including compact sessions.
4078
4124
  */
4079
4125
  get spaceId() {
4080
- return this.auth?.tinyCloudSession?.spaceId;
4126
+ return this.auth?.tinyCloudSession?.spaceId ?? this._activeServiceSession?.spaceId;
4081
4127
  }
4082
4128
  /**
4083
4129
  * Get the account space ID for this wallet identity.
@@ -4125,6 +4171,12 @@ var _TinyCloudNode = class _TinyCloudNode {
4125
4171
  get restorableSession() {
4126
4172
  return this.currentTinyCloudSession();
4127
4173
  }
4174
+ /** The active, verified replica stamp. Undefined when the replica is refused or the session has ended. */
4175
+ get replicaStamp() {
4176
+ const stamp = this.replicaSessionStamp;
4177
+ if (!stamp || this.replicaRestoreGate !== void 0 || !this.currentTinyCloudSession()) return void 0;
4178
+ return { incarnation: stamp.incarnation, lifecycleEpoch: stamp.lifecycleEpoch };
4179
+ }
4128
4180
  /**
4129
4181
  * Sign out and end the session on this instance.
4130
4182
  *
@@ -4145,19 +4197,61 @@ var _TinyCloudNode = class _TinyCloudNode {
4145
4197
  }
4146
4198
  async endSession() {
4147
4199
  const leavingAddress = this.address ?? this._restoredTcSession?.address;
4200
+ const leavingChainId = this._chainId;
4201
+ const leavingPrincipal = this.replicaSessionPrincipal ?? (leavingAddress ? (0, import_sdk_core10.pkhDid)(leavingAddress, leavingChainId) : void 0);
4148
4202
  const leavingAuth = this.auth;
4203
+ const leavingContext = this._serviceContext;
4204
+ let denied;
4149
4205
  try {
4150
4206
  this.teardownSession();
4151
4207
  } finally {
4208
+ const denial = leavingPrincipal ? this.denyReplicaGate({ reason: "sign_out", principal: leavingPrincipal }, leavingContext) : Promise.resolve(void 0);
4152
4209
  try {
4153
- await this.accountRegistryTail;
4154
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
4210
+ denied = await denial;
4155
4211
  } finally {
4156
- if (leavingAddress) {
4157
- await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
4212
+ try {
4213
+ await this.accountRegistryTail;
4214
+ await this.quiesceReplicaRuntime();
4215
+ } finally {
4216
+ if (leavingAddress) {
4217
+ await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
4218
+ }
4158
4219
  }
4159
4220
  }
4160
4221
  }
4222
+ if (denied?.status === "unguarded") {
4223
+ throw new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`);
4224
+ }
4225
+ }
4226
+ /**
4227
+ * Step 1 of replica sign-out. Never rejects: failures come back as `unguarded` so the session
4228
+ * is still cleared. Returns `undefined` when replication is off.
4229
+ */
4230
+ async denyReplicaGate(input, context = this._serviceContext) {
4231
+ const replication = this.config.replication;
4232
+ if (!replication?.enabled) return void 0;
4233
+ try {
4234
+ const lifecycle = replication.storage.lifecycle;
4235
+ if (lifecycle?.kind !== (replication.storage.kind === "indexeddb" ? "indexeddb" : "file")) {
4236
+ return { status: "unguarded", code: "gate_unavailable" };
4237
+ }
4238
+ const result = await lifecycle.deny(input);
4239
+ if (result.status === "denied" || result.status === "deleted" || result.status === "marker") {
4240
+ try {
4241
+ (await getNodeReplicationLoaders().authority()).reportReplicaDenial(
4242
+ result,
4243
+ input.reason,
4244
+ replication.onEvent,
4245
+ context
4246
+ );
4247
+ } catch {
4248
+ }
4249
+ }
4250
+ return result;
4251
+ } catch (error) {
4252
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "gate_unavailable";
4253
+ return { status: "unguarded", code };
4254
+ }
4161
4255
  }
4162
4256
  /**
4163
4257
  * Synchronously drop every piece of in-memory session authority and
@@ -4176,15 +4270,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4176
4270
  this._activeServiceSession = void 0;
4177
4271
  oldGraph.retire();
4178
4272
  oldCore?.retireServices();
4179
- oldAuth?.resetSession(
4180
- this.sessionManager,
4181
- this.sessionKeyId,
4182
- this.explicitHost ? [this.explicitHost] : void 0
4183
- );
4184
4273
  this.config.host = this.explicitHost ?? DEFAULT_HOST;
4185
4274
  this._chainId = 1;
4186
4275
  this._restoredNodeFeatures = void 0;
4187
- this._activeServiceSession = void 0;
4188
4276
  this._serviceContext = void 0;
4189
4277
  this._kv = void 0;
4190
4278
  this._sql = void 0;
@@ -4209,6 +4297,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4209
4297
  this._bootstrapStatus = { skipped: false };
4210
4298
  this.replicationDelegateSession = void 0;
4211
4299
  this.replicationScopeError = void 0;
4300
+ this.replicaSessionPrincipal = void 0;
4301
+ this.replicaSessionStamp = void 0;
4302
+ this.replicaRestoreGate = void 0;
4212
4303
  const sessionKey = createDefaultSessionKey(this.wasmBindings);
4213
4304
  this.sessionManager = sessionKey.manager;
4214
4305
  this.sessionKeyId = sessionKey.keyId;
@@ -4218,6 +4309,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4218
4309
  sessionKey.keyId,
4219
4310
  this.explicitHost ? [this.explicitHost] : void 0
4220
4311
  );
4312
+ this.replicaSessionPrincipal = void 0;
4313
+ this.replicaSessionStamp = void 0;
4314
+ this.replicaRestoreGate = void 0;
4221
4315
  this._serviceGraph = this.createServiceGraphLifetime();
4222
4316
  this.auth = oldAuth;
4223
4317
  this.tc = this.auth ? new import_sdk_core10.TinyCloud(this.auth, {
@@ -4247,11 +4341,25 @@ var _TinyCloudNode = class _TinyCloudNode {
4247
4341
  "Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
4248
4342
  );
4249
4343
  }
4344
+ await this.signInWith(() => this.tc.signIn(options));
4345
+ }
4346
+ /**
4347
+ * Fresh OpenKey sign-in. The callback receives the SDK's original SIWE and
4348
+ * session JWK before approval; the signed result is verified against those
4349
+ * bytes before the network session or replica stamp is committed.
4350
+ */
4351
+ async signInWithOpenKey(authorizeFn, options) {
4352
+ if (!this.signer || !this.tc || !this.auth) {
4353
+ throw new Error("Cannot signInWithOpenKey() in session-only mode. Provide a signer in config.");
4354
+ }
4355
+ await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
4356
+ }
4357
+ async signInWith(performSignIn) {
4250
4358
  const epoch = this.sessionEpoch;
4251
4359
  const attempt = ++this.latestSessionAttempt;
4252
4360
  await this.signOutInFlight;
4253
4361
  try {
4254
- await this.establishSignIn(epoch, options);
4362
+ await this.establishSignIn(epoch, performSignIn);
4255
4363
  } catch (error) {
4256
4364
  if (epoch !== this.sessionEpoch && attempt === this.latestSessionAttempt) {
4257
4365
  await this.endSession();
@@ -4259,7 +4367,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4259
4367
  throw error;
4260
4368
  }
4261
4369
  }
4262
- async establishSignIn(epoch, options) {
4370
+ async establishSignIn(epoch, performSignIn) {
4263
4371
  if (!this.signer || !this.tc) {
4264
4372
  throw new Error("Not signed in. Call signIn() first.");
4265
4373
  }
@@ -4269,6 +4377,12 @@ var _TinyCloudNode = class _TinyCloudNode {
4269
4377
  const address = (0, import_sdk_core10.canonicalizeAddress)(await this.signer.getAddress());
4270
4378
  const chainId = await this.signer.getChainId();
4271
4379
  this.assertSessionEpoch(epoch, "Sign-in");
4380
+ const nextPrincipal = (0, import_sdk_core10.pkhDid)(address, chainId);
4381
+ const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? (0, import_sdk_core10.pkhDid)(this.address, this._chainId) : void 0);
4382
+ if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
4383
+ const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
4384
+ this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
4385
+ }
4272
4386
  if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
4273
4387
  this._address = address;
4274
4388
  this._chainId = chainId;
@@ -4286,7 +4400,20 @@ var _TinyCloudNode = class _TinyCloudNode {
4286
4400
  this.runtimePermissionGrants = [];
4287
4401
  this.confirmedHostedSpaceIds.clear();
4288
4402
  this.hostedOwnedSpaceIds.clear();
4289
- await this.tc.signIn(options);
4403
+ const signInAttempt = this.latestSessionAttempt;
4404
+ const previousReplicaGate = this.replicaRestoreGate;
4405
+ const previousReplicaStamp = this.replicaSessionStamp;
4406
+ this.replicaRestoreGate = void 0;
4407
+ this.replicaSessionStamp = void 0;
4408
+ try {
4409
+ await performSignIn();
4410
+ } catch (error) {
4411
+ if (epoch === this.sessionEpoch && signInAttempt === this.latestSessionAttempt) {
4412
+ this.replicaRestoreGate = previousReplicaGate;
4413
+ this.replicaSessionStamp = previousReplicaStamp;
4414
+ }
4415
+ throw error;
4416
+ }
4290
4417
  this.assertSessionEpoch(epoch, "Sign-in");
4291
4418
  this.cancelOwnedSpaceHosts();
4292
4419
  this.syncResolvedHostFromAuth();
@@ -4294,8 +4421,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4294
4421
  this.hostedOwnedSpaceIds.add(spaceId);
4295
4422
  }
4296
4423
  this.replicationDelegateSession = void 0;
4424
+ this.replicaSessionPrincipal = (0, import_sdk_core10.pkhDid)(address, chainId);
4297
4425
  this.replicationScopeError = void 0;
4298
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
4426
+ await this.quiesceReplicaRuntime();
4299
4427
  this.assertSessionEpoch(epoch, "Sign-in");
4300
4428
  const oldGraph = this._serviceGraph;
4301
4429
  this._serviceGraph = this.createServiceGraphLifetime();
@@ -4882,6 +5010,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4882
5010
  if (this.ownStorageFull) {
4883
5011
  return;
4884
5012
  }
5013
+ if (!this.currentSessionCoversAccountRegistry()) {
5014
+ return;
5015
+ }
4885
5016
  const controller = new AbortController();
4886
5017
  const deadline = setTimeout(
4887
5018
  () => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
@@ -4996,6 +5127,27 @@ var _TinyCloudNode = class _TinyCloudNode {
4996
5127
  (operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
4997
5128
  );
4998
5129
  }
5130
+ /**
5131
+ * Whether the current primary session's recap covers the account index read
5132
+ * (`tinycloud.sql/read` on the owned account space's `account` database),
5133
+ * the first call of every registry sync. As in
5134
+ * {@link currentSessionCanListSpaces}, a session with no parseable recap
5135
+ * keeps today's behavior.
5136
+ */
5137
+ currentSessionCoversAccountRegistry() {
5138
+ const session = this.currentTinyCloudSession();
5139
+ const operations = session ? this.recapOperationsFromSession(session) : [];
5140
+ if (operations.length === 0) {
5141
+ return true;
5142
+ }
5143
+ const indexRead = {
5144
+ spaceId: this.ownedSpaceId(import_sdk_core10.ACCOUNT_REGISTRY_SPACE),
5145
+ service: "sql",
5146
+ path: "account",
5147
+ action: "tinycloud.sql/read"
5148
+ };
5149
+ return operations.some((granted) => this.operationCovers(granted, indexRead));
5150
+ }
4999
5151
  async withAccountRegistryRetry(task, signal) {
5000
5152
  const delays = [250, 1e3, 3e3];
5001
5153
  const stopped = (detail) => {
@@ -5462,11 +5614,12 @@ var _TinyCloudNode = class _TinyCloudNode {
5462
5614
  );
5463
5615
  const stagedHost = resolvedHost ?? this.config.host;
5464
5616
  let restoredNodeFeatures;
5617
+ let replicaProtocolMismatch = false;
5465
5618
  if (this.config.replication?.enabled) {
5466
5619
  try {
5467
5620
  restoredNodeFeatures = (await (0, import_sdk_core10.checkNodeInfo)(stagedHost, this.wasmBindings.protocolVersion())).features;
5468
5621
  } catch (error) {
5469
- if (!(error instanceof import_sdk_core10.VersionCheckError)) throw error;
5622
+ replicaProtocolMismatch = error instanceof import_sdk_core10.ProtocolMismatchError;
5470
5623
  }
5471
5624
  }
5472
5625
  const stagedAddress = restoredAddress;
@@ -5474,28 +5627,55 @@ var _TinyCloudNode = class _TinyCloudNode {
5474
5627
  let replicationPrincipal;
5475
5628
  let compactReplicationSession;
5476
5629
  let replicationScopeError;
5630
+ let replicationScopeUnavailable = false;
5477
5631
  if (this.config.replication?.enabled) {
5478
5632
  if (stagedAddress !== void 0) {
5479
5633
  replicationPrincipal = (0, import_sdk_core10.pkhDid)(stagedAddress, stagedChainId);
5480
5634
  } else {
5481
- const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
5482
- const signedScope = replicationScopeFromSignedSession({
5483
- delegationHeader: sessionData.delegationHeader,
5484
- delegationCid: sessionData.delegationCid,
5485
- verificationMethod: restoredVerificationMethod,
5486
- persistedSpace: sessionData.spaceId
5487
- });
5488
- if (!signedScope) {
5489
- replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
5490
- } else {
5491
- replicationPrincipal = signedScope.principal;
5492
- compactReplicationSession = {
5635
+ try {
5636
+ const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
5637
+ const signedScope = replicationScopeFromSignedSession({
5493
5638
  delegationHeader: sessionData.delegationHeader,
5494
5639
  delegationCid: sessionData.delegationCid,
5495
- spaceId: signedScope.space,
5496
5640
  verificationMethod: restoredVerificationMethod,
5497
- jwk: stagedJwk
5498
- };
5641
+ persistedSpace: sessionData.spaceId
5642
+ });
5643
+ if (!signedScope) {
5644
+ replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
5645
+ } else {
5646
+ replicationPrincipal = signedScope.principal;
5647
+ compactReplicationSession = {
5648
+ delegationHeader: sessionData.delegationHeader,
5649
+ delegationCid: sessionData.delegationCid,
5650
+ spaceId: signedScope.space,
5651
+ verificationMethod: restoredVerificationMethod,
5652
+ jwk: stagedJwk
5653
+ };
5654
+ }
5655
+ } catch {
5656
+ replicationScopeUnavailable = true;
5657
+ replicationScopeError = "Replica scope could not be verified for this network session.";
5658
+ }
5659
+ }
5660
+ }
5661
+ let replicaRestoreGate;
5662
+ let restoredReplicaStamp;
5663
+ const replication = this.config.replication;
5664
+ if (replication?.enabled) {
5665
+ if (replicaProtocolMismatch) {
5666
+ replicaRestoreGate = "gate_unavailable";
5667
+ } else if (replicationPrincipal === void 0) {
5668
+ replicaRestoreGate = replicationScopeUnavailable ? "gate_unavailable" : "principal_mismatch";
5669
+ } else {
5670
+ try {
5671
+ const { restoreReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
5672
+ restoredReplicaStamp = await restoreReplicaSessionStamp(
5673
+ replication.storage,
5674
+ replicationPrincipal,
5675
+ sessionData.replicaStamp ?? void 0
5676
+ );
5677
+ } catch (error) {
5678
+ replicaRestoreGate = replicaRestoreRefusal(error);
5499
5679
  }
5500
5680
  }
5501
5681
  }
@@ -5588,7 +5768,10 @@ var _TinyCloudNode = class _TinyCloudNode {
5588
5768
  }
5589
5769
  this.replicationDelegateSession = compactReplicationSession;
5590
5770
  this.replicationScopeError = replicationScopeError;
5591
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
5771
+ this.replicaRestoreGate = replicaRestoreGate;
5772
+ this.replicaSessionPrincipal = replicationPrincipal;
5773
+ this.replicaSessionStamp = restoredReplicaStamp;
5774
+ await this.quiesceReplicaRuntime();
5592
5775
  if (epoch !== this.sessionEpoch) {
5593
5776
  oldGraph.retire();
5594
5777
  oldCore?.retireServices?.();
@@ -5603,7 +5786,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5603
5786
  detail: replicationScopeError
5604
5787
  });
5605
5788
  }
5606
- await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
5789
+ await this.bindReplicaOrGate(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
5607
5790
  oldCore?.retireServices?.();
5608
5791
  if (epoch !== this.sessionEpoch) {
5609
5792
  if (attempt === this.latestSessionAttempt) await this.endSession();
@@ -5736,6 +5919,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5736
5919
  session: input.serviceSession,
5737
5920
  invoke: graph.invoke,
5738
5921
  invokeAny: graph.invokeAny,
5922
+ signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
5739
5923
  fetch: graph.fetch
5740
5924
  });
5741
5925
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -6079,6 +6263,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6079
6263
  signer: this.signer,
6080
6264
  signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
6081
6265
  wasmBindings: this.wasmBindings,
6266
+ ...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
6082
6267
  sessionManager: this.sessionManager,
6083
6268
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
6084
6269
  domain: this.siweDomain,
@@ -6137,6 +6322,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6137
6322
  signer: this.signer,
6138
6323
  signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
6139
6324
  wasmBindings: this.wasmBindings,
6325
+ ...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
6140
6326
  sessionManager: this.sessionManager,
6141
6327
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
6142
6328
  domain: this.siweDomain,
@@ -6217,7 +6403,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6217
6403
  jwk: session.jwk
6218
6404
  };
6219
6405
  this._serviceContext.setSession(serviceSession);
6220
- await this.bindReplicationRuntime(this._serviceContext, serviceSession, this._kv);
6406
+ await this.bindReplicaOrGate(this._serviceContext, serviceSession, this._kv);
6221
6407
  this.tc.serviceContext.setSession(serviceSession);
6222
6408
  this._vault = this.createVaultService(session.spaceId, this._kv);
6223
6409
  this._vault.initialize(this._serviceContext);
@@ -6611,6 +6797,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6611
6797
  session: serviceSession,
6612
6798
  invoke: graph.invoke,
6613
6799
  invokeAny: graph.invokeAny,
6800
+ signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
6614
6801
  fetch: graph.fetch
6615
6802
  });
6616
6803
  this._spaceService = new import_sdk_core10.SpaceService({
@@ -6746,11 +6933,12 @@ var _TinyCloudNode = class _TinyCloudNode {
6746
6933
  * Both SharingService (single-entry) and
6747
6934
  * {@link TinyCloudNode.delegateTo} (multi-entry) drive this through
6748
6935
  * the same code path so there's exactly one place that touches the
6749
- * WASM boundary.
6936
+ * WASM boundary. `terminal` adds the node's terminal marker, which the
6937
+ * WASM builder cannot emit.
6750
6938
  *
6751
6939
  * @internal
6752
6940
  */
6753
- createDelegationWrapper(params) {
6941
+ createDelegationWrapper(params, terminal = false) {
6754
6942
  const wasmSession = {
6755
6943
  delegationHeader: params.session.delegationHeader,
6756
6944
  delegationCid: params.session.delegationCid,
@@ -6770,8 +6958,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6770
6958
  params.notBeforeSecs
6771
6959
  );
6772
6960
  return {
6773
- delegation: result.delegation,
6774
- cid: result.cid,
6961
+ ...terminal ? (0, import_sdk_core10.markDelegationTerminal)(result.delegation, params.session.jwk) : { delegation: result.delegation, cid: result.cid },
6775
6962
  // Rust serde `rename_all = "camelCase"` emits `delegateDid`
6776
6963
  // (lowercase d); the TypeScript interface uses `delegateDID`
6777
6964
  // (historical, matches Delegation.delegateDID). Normalize here.
@@ -6913,7 +7100,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6913
7100
  };
6914
7101
  const requestBodyDigest = base64UrlEncode(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, import_sdk_core10.canonicalizeEncryptionJson)(body)))));
6915
7102
  const request = { ...body, requestBodyDigest };
6916
- const authorization = authorizationHeader(this.invokeAnyWithRuntimePermissions(serviceSession, [{ spaceId: session.spaceId, service: "kv", path: input.resourcePath, action: "tinycloud.kv/get" }]));
7103
+ const authorization = authorizationHeader(this.wasmBindings.invoke(serviceSession, "kv", input.resourcePath, "tinycloud.kv/get"));
6917
7104
  const response = await fetch(`${this.config.host}/policy/v3/deliveries/authorize`, {
6918
7105
  method: "POST",
6919
7106
  headers: { accept: "application/json", "content-type": "application/json", authorization },
@@ -7228,6 +7415,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7228
7415
  }
7229
7416
  };
7230
7417
  }
7418
+ if (result.status === "aad_mismatch") return { status: "read_failed" };
7231
7419
  return { status: result.status };
7232
7420
  }
7233
7421
  if (!isSecretPayload(result.entry.value)) {
@@ -7903,7 +8091,8 @@ var _TinyCloudNode = class _TinyCloudNode {
7903
8091
  return this.delegationManager.create(params);
7904
8092
  }
7905
8093
  /**
7906
- * Revoke a delegation using the v2 DelegationManager.
8094
+ * Revoke a delegation with the owner wallet when available. Session-only
8095
+ * clients continue to use their delegated revocation authority.
7907
8096
  *
7908
8097
  * @param cid - The CID of the delegation to revoke
7909
8098
  * @returns Result indicating success or failure
@@ -8019,6 +8208,9 @@ var _TinyCloudNode = class _TinyCloudNode {
8019
8208
  effectiveExpiration = sessionExpiry;
8020
8209
  }
8021
8210
  if (options?.forceWalletSign) {
8211
+ if (options.disableSubDelegation) {
8212
+ throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
8213
+ }
8022
8214
  if (expandedEntries.length > 1) {
8023
8215
  throw new Error(
8024
8216
  "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."
@@ -8054,7 +8246,8 @@ var _TinyCloudNode = class _TinyCloudNode {
8054
8246
  plan.effectiveExpiration,
8055
8247
  plan.grant,
8056
8248
  plan.operations,
8057
- onPrepared
8249
+ onPrepared,
8250
+ options?.disableSubDelegation === true
8058
8251
  );
8059
8252
  return { delegation: delegation2, prompted: false };
8060
8253
  }
@@ -8063,7 +8256,8 @@ var _TinyCloudNode = class _TinyCloudNode {
8063
8256
  expandedEntries,
8064
8257
  effectiveExpiration,
8065
8258
  session,
8066
- onPrepared
8259
+ onPrepared,
8260
+ options?.disableSubDelegation === true
8067
8261
  );
8068
8262
  return { delegation, prompted: false };
8069
8263
  }
@@ -8178,34 +8372,67 @@ var _TinyCloudNode = class _TinyCloudNode {
8178
8372
  return auth.replicationSignInEntries();
8179
8373
  }
8180
8374
  async replicationAuthority() {
8181
- const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
8375
+ const { createReplicationAuthority, mintNodeReplicationGrant, verifiedOwnerConsentAnchor } = await getNodeReplicationLoaders().authority();
8182
8376
  return createReplicationAuthority({
8183
8377
  replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
8378
+ verifiedConsentAnchor: (session) => {
8379
+ const current = this.currentTinyCloudSession();
8380
+ return session === current && current !== void 0 ? verifiedOwnerConsentAnchor(current, this.wasmBindings, exactVerifiedRecap) : null;
8381
+ },
8184
8382
  siweExpiration: (siwe) => extractSiweExpiration(siwe),
8185
8383
  planDelegation: (entries, options) => this.planDelegation(entries, options),
8186
- mintDelegation: async (deviceDid, entries) => {
8187
- const result = await this.delegateTo((0, import_sdk_core10.principalDid)(deviceDid), entries);
8188
- return {
8189
- ucan: result.delegation.delegationHeader.Authorization,
8190
- expiresAt: result.delegation.expiry.getTime()
8191
- };
8192
- }
8384
+ mintDelegation: (deviceDid, entries, options) => mintNodeReplicationGrant(
8385
+ deviceDid,
8386
+ entries,
8387
+ options,
8388
+ options?.renewalOnly ? this.currentTinyCloudSession() : void 0,
8389
+ this
8390
+ )
8193
8391
  });
8194
8392
  }
8195
- async bindReplicationRuntime(context, session, kv, principal) {
8393
+ async quiesceReplicaRuntime() {
8196
8394
  if (!this.replicationRuntime) return;
8197
- const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : (0, import_sdk_core10.pkhDid)(this._address, this._chainId));
8198
- if (identityPrincipal === void 0) return;
8199
- const runtime = await this.replicationRuntime;
8200
- const authority = await this.replicationAuthority();
8201
- await runtime.bind({
8202
- context,
8203
- session,
8204
- principal: identityPrincipal,
8205
- authority,
8206
- nodeFeatures: this.replicationNodeFeatures,
8207
- primaryKV: [{ space: session.spaceId, kv }]
8208
- });
8395
+ try {
8396
+ await (await this.replicationRuntime).unbind();
8397
+ } catch {
8398
+ this.replicaRestoreGate = "gate_unavailable";
8399
+ }
8400
+ }
8401
+ async bindReplicaOrGate(context, session, kv, principal) {
8402
+ if (!this.replicationRuntime) return;
8403
+ const gate = this.replicaRestoreGate;
8404
+ const epoch = this.sessionEpoch;
8405
+ const attempt = this.latestSessionAttempt;
8406
+ try {
8407
+ const runtime = await this.replicationRuntime;
8408
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context || gate !== this.replicaRestoreGate) return;
8409
+ if (gate) {
8410
+ await runtime.bindGatedNetworkOnly({
8411
+ context,
8412
+ session,
8413
+ gate,
8414
+ primaryKV: [{ space: session.spaceId, kv }]
8415
+ }, getNodeReplicationLoaders().gatedNetwork);
8416
+ return;
8417
+ }
8418
+ const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : (0, import_sdk_core10.pkhDid)(this._address, this._chainId));
8419
+ if (identityPrincipal === void 0) return;
8420
+ const authority = await this.replicationAuthority();
8421
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
8422
+ await runtime.bind({
8423
+ context,
8424
+ session,
8425
+ principal: identityPrincipal,
8426
+ stamp: this.replicaSessionStamp,
8427
+ authority,
8428
+ nodeFeatures: this.replicationNodeFeatures,
8429
+ primaryKV: [{ space: session.spaceId, kv }]
8430
+ });
8431
+ } catch {
8432
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
8433
+ this.replicaRestoreGate = "gate_unavailable";
8434
+ await this.quiesceReplicaRuntime();
8435
+ }
8209
8436
  }
8210
8437
  /** Stable control facade for this node's optional replication runtime. */
8211
8438
  get replication() {
@@ -8261,7 +8488,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8261
8488
  *
8262
8489
  * @internal
8263
8490
  */
8264
- async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared) {
8491
+ async createDelegationViaWasmPath(did, entries, expirationTime, session, onPrepared, disableSubDelegation = false) {
8265
8492
  if (entries.length === 0) {
8266
8493
  throw new Error(
8267
8494
  "createDelegationViaWasmPath requires a non-empty entries array"
@@ -8321,7 +8548,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8321
8548
  spaceId,
8322
8549
  abilities,
8323
8550
  expirationSecs
8324
- });
8551
+ }, disableSubDelegation);
8325
8552
  const primary = result.resources[0];
8326
8553
  const delegationHeader = { Authorization: result.delegation };
8327
8554
  const delegation = {
@@ -8331,7 +8558,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8331
8558
  path: primary.path,
8332
8559
  actions: primary.actions,
8333
8560
  resources: result.resources,
8334
- disableSubDelegation: false,
8561
+ disableSubDelegation,
8335
8562
  expiry: result.expiry,
8336
8563
  delegateDID: did,
8337
8564
  ownerAddress: session.address,
@@ -8353,7 +8580,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8353
8580
  }
8354
8581
  return delegation;
8355
8582
  }
8356
- async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared) {
8583
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant, requestedOperations, onPrepared, disableSubDelegation = false) {
8357
8584
  this.assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant);
8358
8585
  this.assertDelegationCaveatsPreservable(entries);
8359
8586
  const result = this.createDelegationWrapper({
@@ -8362,7 +8589,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8362
8589
  spaceId: grant.session.spaceId,
8363
8590
  abilities: this.permissionsToAbilities(entries),
8364
8591
  expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
8365
- });
8592
+ }, disableSubDelegation);
8366
8593
  const primary = result.resources[0];
8367
8594
  const delegationHeader = { Authorization: result.delegation };
8368
8595
  const targetHost = grant.delegation.host ?? this.config.host;
@@ -8373,7 +8600,7 @@ var _TinyCloudNode = class _TinyCloudNode {
8373
8600
  path: primary.path,
8374
8601
  actions: primary.actions,
8375
8602
  resources: result.resources,
8376
- disableSubDelegation: false,
8603
+ disableSubDelegation,
8377
8604
  expiry: result.expiry,
8378
8605
  delegateDID: did,
8379
8606
  ownerAddress: grant.delegation.ownerAddress,
@@ -8957,11 +9184,10 @@ var _TinyCloudNode = class _TinyCloudNode {
8957
9184
  params.spaceIdOverride
8958
9185
  );
8959
9186
  try {
8960
- const result = await this.delegateTo(
8961
- resolvedDelegateDID,
8962
- entries,
8963
- params.expiryMs !== void 0 ? { expiry: params.expiryMs } : void 0
8964
- );
9187
+ const result = await this.delegateTo(resolvedDelegateDID, entries, {
9188
+ ...params.expiryMs !== void 0 ? { expiry: params.expiryMs } : {},
9189
+ disableSubDelegation: params.disableSubDelegation
9190
+ });
8965
9191
  return result.delegation;
8966
9192
  } catch (err2) {
8967
9193
  if (err2 instanceof import_sdk_core10.PermissionNotInManifestError) {
@@ -8982,6 +9208,9 @@ var _TinyCloudNode = class _TinyCloudNode {
8982
9208
  * @internal
8983
9209
  */
8984
9210
  async createDelegationWalletPath(params) {
9211
+ if (params.disableSubDelegation) {
9212
+ throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
9213
+ }
8985
9214
  if (!this.signer) {
8986
9215
  throw new Error("Cannot createDelegation() in session-only mode. Requires wallet mode.");
8987
9216
  }
@@ -9037,7 +9266,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9037
9266
  spaceId: params.spaceIdOverride ?? session.spaceId,
9038
9267
  path: params.path,
9039
9268
  actions: params.actions,
9040
- disableSubDelegation: params.disableSubDelegation ?? false,
9269
+ disableSubDelegation: false,
9041
9270
  expiry: expirationTime,
9042
9271
  delegateDID: params.delegateDID,
9043
9272
  ownerAddress: session.address,
@@ -9080,7 +9309,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9080
9309
  spaceId: publicSpaceId,
9081
9310
  path: "",
9082
9311
  actions: [import_sdk_core10.KV.GET, import_sdk_core10.KV.PUT, import_sdk_core10.KV.METADATA],
9083
- disableSubDelegation: params.disableSubDelegation ?? false,
9312
+ disableSubDelegation: false,
9084
9313
  expiry: expirationTime,
9085
9314
  delegateDID: params.delegateDID,
9086
9315
  ownerAddress: session.address,
@@ -9247,6 +9476,9 @@ var _TinyCloudNode = class _TinyCloudNode {
9247
9476
  if (parentDelegation.disableSubDelegation) {
9248
9477
  throw new Error("Parent delegation does not allow sub-delegation");
9249
9478
  }
9479
+ if (params.disableSubDelegation) {
9480
+ throw new Error(TERMINAL_DELEGATION_REQUIRES_SESSION_KEY);
9481
+ }
9250
9482
  const parentResources = parentDelegation.resources ?? [{
9251
9483
  service: parentDelegation.actions[0]?.split("/", 1)[0]?.replace(/^tinycloud\./, "") ?? "kv",
9252
9484
  space: parentDelegation.spaceId,
@@ -9319,7 +9551,7 @@ var _TinyCloudNode = class _TinyCloudNode {
9319
9551
  spaceId: parentDelegation.spaceId,
9320
9552
  path: requestedResources[0].path,
9321
9553
  actions: requestedResources[0].actions,
9322
- disableSubDelegation: params.disableSubDelegation ?? false,
9554
+ disableSubDelegation: false,
9323
9555
  expiry: actualExpiry,
9324
9556
  delegateDID: params.delegateDID,
9325
9557
  ownerAddress: parentDelegation.ownerAddress,
@@ -9436,6 +9668,7 @@ var import_sdk_core22 = require("@tinycloud/sdk-core");
9436
9668
  expandActionShortNames,
9437
9669
  expandPermissionEntries,
9438
9670
  expandPermissionEntry,
9671
+ getNodeReplicationLoaders,
9439
9672
  getTinyCloudDebugLogs,
9440
9673
  installTinyCloudDebugGlobals,
9441
9674
  isCapabilitySubset,