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

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-BchWB_zv.d.cts} +162 -283
  2. package/dist/{WasmKeyProvider-DEdSyQ0I.d.ts → TinyCloudNode-BchWB_zv.d.ts} +162 -283
  3. package/dist/WasmKeyProvider-5Ee7fcAw.d.cts +217 -0
  4. package/dist/WasmKeyProvider-5Ee7fcAw.d.ts +217 -0
  5. package/dist/authority-4HXYteKF.d.ts +118 -0
  6. package/dist/authority-lrA-698X.d.cts +118 -0
  7. package/dist/core.cjs +368 -147
  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 +374 -155
  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 +2702 -524
  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 +2702 -530
  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,
@@ -3499,6 +3479,20 @@ function accountIndexWarningDiagnostic(error) {
3499
3479
  }
3500
3480
  return diagnostic;
3501
3481
  }
3482
+ function replicaRestoreRefusal(error) {
3483
+ if (error !== null && typeof error === "object" && "code" in error && error.code === "REPLICA_GATED" && "gate" in error) {
3484
+ switch (error.gate) {
3485
+ case "not_established":
3486
+ case "stamp_stale":
3487
+ case "principal_mismatch":
3488
+ case "purge_pending":
3489
+ case "logout_pending":
3490
+ case "gate_unavailable":
3491
+ return error.gate;
3492
+ }
3493
+ }
3494
+ return "gate_unavailable";
3495
+ }
3502
3496
  var _TinyCloudNode = class _TinyCloudNode {
3503
3497
  /**
3504
3498
  * Create a new TinyCloudNode instance.
@@ -3528,6 +3522,37 @@ var _TinyCloudNode = class _TinyCloudNode {
3528
3522
  this.auth = null;
3529
3523
  this.tc = null;
3530
3524
  this._chainId = 1;
3525
+ /** Mints the replica session stamp on each fresh sign-in (TC-858 §4.1). Undefined when replication is off. */
3526
+ this.establishReplicaStamp = async (session) => {
3527
+ const replication = this.config.replication;
3528
+ if (!replication?.enabled) return void 0;
3529
+ const epoch = this.sessionEpoch;
3530
+ const attempt = this.latestSessionAttempt;
3531
+ try {
3532
+ const { verifiedOwnerConsentAnchor, establishReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
3533
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
3534
+ const anchor = verifiedOwnerConsentAnchor(session, this.wasmBindings, exactVerifiedRecap);
3535
+ if (anchor === null) {
3536
+ throw Object.assign(new Error("Replica sign-in requires a verified finite owner SIWE and exact-space KV get+sync."), {
3537
+ code: "REPLICA_GATED",
3538
+ gate: "not_established"
3539
+ });
3540
+ }
3541
+ const stamp = await establishReplicaSessionStamp(
3542
+ replication.storage,
3543
+ pkhDid2(session.address, session.chainId),
3544
+ anchor
3545
+ );
3546
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt) return void 0;
3547
+ this.replicaSessionStamp = stamp;
3548
+ return stamp;
3549
+ } catch (error) {
3550
+ if (epoch === this.sessionEpoch && attempt === this.latestSessionAttempt) {
3551
+ this.replicaRestoreGate = replicaRestoreRefusal(error);
3552
+ }
3553
+ return void 0;
3554
+ }
3555
+ };
3531
3556
  this._baseVaults = /* @__PURE__ */ new Map();
3532
3557
  this._baseSecrets = /* @__PURE__ */ new Map();
3533
3558
  this._secrets = /* @__PURE__ */ new Map();
@@ -3652,7 +3677,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3652
3677
  });
3653
3678
  return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
3654
3679
  };
3655
- assertValidReplicationConfig(config.replication);
3680
+ if (config.replication?.enabled) getNodeReplicationLoaders().validateConfig(config.replication);
3656
3681
  this.explicitHost = config.host;
3657
3682
  this.config = {
3658
3683
  ...config,
@@ -3667,6 +3692,16 @@ var _TinyCloudNode = class _TinyCloudNode {
3667
3692
  this.replicationRuntime = runtime;
3668
3693
  this.replicationControl = {
3669
3694
  status: async () => {
3695
+ const gate = this.replicaRestoreGate;
3696
+ if (gate) return this.config.replication.prefixes.map((prefix) => ({
3697
+ prefix,
3698
+ state: "gated",
3699
+ gate,
3700
+ errorCode: "REPLICA_GATED",
3701
+ pending: { inFlight: 0, committed: 0, ambiguous: 0 },
3702
+ pinned: [],
3703
+ lagMs: null
3704
+ }));
3670
3705
  const status = await runtime.then((ready) => ready.control.status());
3671
3706
  const scopeError = this.replicationScopeError;
3672
3707
  if (!scopeError) return status;
@@ -3683,7 +3718,15 @@ var _TinyCloudNode = class _TinyCloudNode {
3683
3718
  lagMs: null
3684
3719
  }));
3685
3720
  },
3686
- sync: async (input) => (await runtime).control.sync(input),
3721
+ sync: async (input) => {
3722
+ if (this.replicaRestoreGate) {
3723
+ throw Object.assign(new Error("Replica restore is gated."), {
3724
+ code: "REPLICA_GATED",
3725
+ gate: this.replicaRestoreGate
3726
+ });
3727
+ }
3728
+ return (await runtime).control.sync(input);
3729
+ },
3687
3730
  purge: (input) => {
3688
3731
  const instance = this.replicationRuntimeInstance;
3689
3732
  return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
@@ -3899,6 +3942,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3899
3942
  signer: this.signer,
3900
3943
  signStrategy: config.signStrategy ?? { type: "auto-sign" },
3901
3944
  wasmBindings: this.wasmBindings,
3945
+ ...config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
3902
3946
  sessionManager: this.sessionManager,
3903
3947
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
3904
3948
  domain: this.siweDomain,
@@ -4096,11 +4140,10 @@ var _TinyCloudNode = class _TinyCloudNode {
4096
4140
  return this.signer === null;
4097
4141
  }
4098
4142
  /**
4099
- * Get the space ID for this user.
4100
- * Available after signIn().
4143
+ * Primary space of the signed-in or restored network session, including compact sessions.
4101
4144
  */
4102
4145
  get spaceId() {
4103
- return this.auth?.tinyCloudSession?.spaceId;
4146
+ return this.auth?.tinyCloudSession?.spaceId ?? this._activeServiceSession?.spaceId;
4104
4147
  }
4105
4148
  /**
4106
4149
  * Get the account space ID for this wallet identity.
@@ -4148,6 +4191,12 @@ var _TinyCloudNode = class _TinyCloudNode {
4148
4191
  get restorableSession() {
4149
4192
  return this.currentTinyCloudSession();
4150
4193
  }
4194
+ /** The active, verified replica stamp. Undefined when the replica is refused or the session has ended. */
4195
+ get replicaStamp() {
4196
+ const stamp = this.replicaSessionStamp;
4197
+ if (!stamp || this.replicaRestoreGate !== void 0 || !this.currentTinyCloudSession()) return void 0;
4198
+ return { incarnation: stamp.incarnation, lifecycleEpoch: stamp.lifecycleEpoch };
4199
+ }
4151
4200
  /**
4152
4201
  * Sign out and end the session on this instance.
4153
4202
  *
@@ -4168,18 +4217,60 @@ var _TinyCloudNode = class _TinyCloudNode {
4168
4217
  }
4169
4218
  async endSession() {
4170
4219
  const leavingAddress = this.address ?? this._restoredTcSession?.address;
4220
+ const leavingChainId = this._chainId;
4221
+ const leavingPrincipal = this.replicaSessionPrincipal ?? (leavingAddress ? pkhDid2(leavingAddress, leavingChainId) : void 0);
4171
4222
  const leavingAuth = this.auth;
4223
+ const leavingContext = this._serviceContext;
4224
+ let denied;
4172
4225
  try {
4173
4226
  this.teardownSession();
4174
4227
  } finally {
4228
+ const denial = leavingPrincipal ? this.denyReplicaGate({ reason: "sign_out", principal: leavingPrincipal }, leavingContext) : Promise.resolve(void 0);
4175
4229
  try {
4176
- await this.accountRegistryTail;
4177
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
4230
+ denied = await denial;
4178
4231
  } finally {
4179
- if (leavingAddress) {
4180
- await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
4232
+ try {
4233
+ await this.accountRegistryTail;
4234
+ await this.quiesceReplicaRuntime();
4235
+ } finally {
4236
+ if (leavingAddress) {
4237
+ await (leavingAuth ? leavingAuth.clearPersistedSession(leavingAddress) : this.config.sessionStorage?.clear(leavingAddress));
4238
+ }
4239
+ }
4240
+ }
4241
+ }
4242
+ if (denied?.status === "unguarded") {
4243
+ throw new Error(`Replica gate was not denied (${denied.code ?? "unknown"}); the replica may still be restorable.`);
4244
+ }
4245
+ }
4246
+ /**
4247
+ * Step 1 of replica sign-out. Never rejects: failures come back as `unguarded` so the session
4248
+ * is still cleared. Returns `undefined` when replication is off.
4249
+ */
4250
+ async denyReplicaGate(input, context = this._serviceContext) {
4251
+ const replication = this.config.replication;
4252
+ if (!replication?.enabled) return void 0;
4253
+ try {
4254
+ const lifecycle = replication.storage.lifecycle;
4255
+ if (lifecycle?.kind !== (replication.storage.kind === "indexeddb" ? "indexeddb" : "file")) {
4256
+ return { status: "unguarded", code: "gate_unavailable" };
4257
+ }
4258
+ const result = await lifecycle.deny(input);
4259
+ if (result.status === "denied" || result.status === "deleted" || result.status === "marker") {
4260
+ try {
4261
+ (await getNodeReplicationLoaders().authority()).reportReplicaDenial(
4262
+ result,
4263
+ input.reason,
4264
+ replication.onEvent,
4265
+ context
4266
+ );
4267
+ } catch {
4181
4268
  }
4182
4269
  }
4270
+ return result;
4271
+ } catch (error) {
4272
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : "gate_unavailable";
4273
+ return { status: "unguarded", code };
4183
4274
  }
4184
4275
  }
4185
4276
  /**
@@ -4199,15 +4290,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4199
4290
  this._activeServiceSession = void 0;
4200
4291
  oldGraph.retire();
4201
4292
  oldCore?.retireServices();
4202
- oldAuth?.resetSession(
4203
- this.sessionManager,
4204
- this.sessionKeyId,
4205
- this.explicitHost ? [this.explicitHost] : void 0
4206
- );
4207
4293
  this.config.host = this.explicitHost ?? DEFAULT_HOST;
4208
4294
  this._chainId = 1;
4209
4295
  this._restoredNodeFeatures = void 0;
4210
- this._activeServiceSession = void 0;
4211
4296
  this._serviceContext = void 0;
4212
4297
  this._kv = void 0;
4213
4298
  this._sql = void 0;
@@ -4232,6 +4317,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4232
4317
  this._bootstrapStatus = { skipped: false };
4233
4318
  this.replicationDelegateSession = void 0;
4234
4319
  this.replicationScopeError = void 0;
4320
+ this.replicaSessionPrincipal = void 0;
4321
+ this.replicaSessionStamp = void 0;
4322
+ this.replicaRestoreGate = void 0;
4235
4323
  const sessionKey = createDefaultSessionKey(this.wasmBindings);
4236
4324
  this.sessionManager = sessionKey.manager;
4237
4325
  this.sessionKeyId = sessionKey.keyId;
@@ -4241,6 +4329,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4241
4329
  sessionKey.keyId,
4242
4330
  this.explicitHost ? [this.explicitHost] : void 0
4243
4331
  );
4332
+ this.replicaSessionPrincipal = void 0;
4333
+ this.replicaSessionStamp = void 0;
4334
+ this.replicaRestoreGate = void 0;
4244
4335
  this._serviceGraph = this.createServiceGraphLifetime();
4245
4336
  this.auth = oldAuth;
4246
4337
  this.tc = this.auth ? new TinyCloud(this.auth, {
@@ -4270,11 +4361,25 @@ var _TinyCloudNode = class _TinyCloudNode {
4270
4361
  "Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
4271
4362
  );
4272
4363
  }
4364
+ await this.signInWith(() => this.tc.signIn(options));
4365
+ }
4366
+ /**
4367
+ * Fresh OpenKey sign-in. The callback receives the SDK's original SIWE and
4368
+ * session JWK before approval; the signed result is verified against those
4369
+ * bytes before the network session or replica stamp is committed.
4370
+ */
4371
+ async signInWithOpenKey(authorizeFn, options) {
4372
+ if (!this.signer || !this.tc || !this.auth) {
4373
+ throw new Error("Cannot signInWithOpenKey() in session-only mode. Provide a signer in config.");
4374
+ }
4375
+ await this.signInWith(() => this.auth.signInWithOpenKey(authorizeFn, options));
4376
+ }
4377
+ async signInWith(performSignIn) {
4273
4378
  const epoch = this.sessionEpoch;
4274
4379
  const attempt = ++this.latestSessionAttempt;
4275
4380
  await this.signOutInFlight;
4276
4381
  try {
4277
- await this.establishSignIn(epoch, options);
4382
+ await this.establishSignIn(epoch, performSignIn);
4278
4383
  } catch (error) {
4279
4384
  if (epoch !== this.sessionEpoch && attempt === this.latestSessionAttempt) {
4280
4385
  await this.endSession();
@@ -4282,7 +4387,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4282
4387
  throw error;
4283
4388
  }
4284
4389
  }
4285
- async establishSignIn(epoch, options) {
4390
+ async establishSignIn(epoch, performSignIn) {
4286
4391
  if (!this.signer || !this.tc) {
4287
4392
  throw new Error("Not signed in. Call signIn() first.");
4288
4393
  }
@@ -4292,6 +4397,12 @@ var _TinyCloudNode = class _TinyCloudNode {
4292
4397
  const address = canonicalizeAddress2(await this.signer.getAddress());
4293
4398
  const chainId = await this.signer.getChainId();
4294
4399
  this.assertSessionEpoch(epoch, "Sign-in");
4400
+ const nextPrincipal = pkhDid2(address, chainId);
4401
+ const priorPrincipal = this.replicaSessionPrincipal ?? (this.address ? pkhDid2(this.address, this._chainId) : void 0);
4402
+ if (this.config.replication?.enabled && priorPrincipal && priorPrincipal !== nextPrincipal) {
4403
+ const denial = await this.denyReplicaGate({ reason: "account_switch", principal: priorPrincipal, next: nextPrincipal });
4404
+ this.replicaRestoreGate = denial?.status === "unguarded" ? "gate_unavailable" : "stamp_stale";
4405
+ }
4295
4406
  if (address !== this._address || chainId !== this._chainId) this.storageFull.clear();
4296
4407
  this._address = address;
4297
4408
  this._chainId = chainId;
@@ -4309,7 +4420,20 @@ var _TinyCloudNode = class _TinyCloudNode {
4309
4420
  this.runtimePermissionGrants = [];
4310
4421
  this.confirmedHostedSpaceIds.clear();
4311
4422
  this.hostedOwnedSpaceIds.clear();
4312
- await this.tc.signIn(options);
4423
+ const signInAttempt = this.latestSessionAttempt;
4424
+ const previousReplicaGate = this.replicaRestoreGate;
4425
+ const previousReplicaStamp = this.replicaSessionStamp;
4426
+ this.replicaRestoreGate = void 0;
4427
+ this.replicaSessionStamp = void 0;
4428
+ try {
4429
+ await performSignIn();
4430
+ } catch (error) {
4431
+ if (epoch === this.sessionEpoch && signInAttempt === this.latestSessionAttempt) {
4432
+ this.replicaRestoreGate = previousReplicaGate;
4433
+ this.replicaSessionStamp = previousReplicaStamp;
4434
+ }
4435
+ throw error;
4436
+ }
4313
4437
  this.assertSessionEpoch(epoch, "Sign-in");
4314
4438
  this.cancelOwnedSpaceHosts();
4315
4439
  this.syncResolvedHostFromAuth();
@@ -4317,8 +4441,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4317
4441
  this.hostedOwnedSpaceIds.add(spaceId);
4318
4442
  }
4319
4443
  this.replicationDelegateSession = void 0;
4444
+ this.replicaSessionPrincipal = pkhDid2(address, chainId);
4320
4445
  this.replicationScopeError = void 0;
4321
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
4446
+ await this.quiesceReplicaRuntime();
4322
4447
  this.assertSessionEpoch(epoch, "Sign-in");
4323
4448
  const oldGraph = this._serviceGraph;
4324
4449
  this._serviceGraph = this.createServiceGraphLifetime();
@@ -4905,6 +5030,9 @@ var _TinyCloudNode = class _TinyCloudNode {
4905
5030
  if (this.ownStorageFull) {
4906
5031
  return;
4907
5032
  }
5033
+ if (!this.currentSessionCoversAccountRegistry()) {
5034
+ return;
5035
+ }
4908
5036
  const controller = new AbortController();
4909
5037
  const deadline = setTimeout(
4910
5038
  () => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
@@ -5019,6 +5147,27 @@ var _TinyCloudNode = class _TinyCloudNode {
5019
5147
  (operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
5020
5148
  );
5021
5149
  }
5150
+ /**
5151
+ * Whether the current primary session's recap covers the account index read
5152
+ * (`tinycloud.sql/read` on the owned account space's `account` database),
5153
+ * the first call of every registry sync. As in
5154
+ * {@link currentSessionCanListSpaces}, a session with no parseable recap
5155
+ * keeps today's behavior.
5156
+ */
5157
+ currentSessionCoversAccountRegistry() {
5158
+ const session = this.currentTinyCloudSession();
5159
+ const operations = session ? this.recapOperationsFromSession(session) : [];
5160
+ if (operations.length === 0) {
5161
+ return true;
5162
+ }
5163
+ const indexRead = {
5164
+ spaceId: this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2),
5165
+ service: "sql",
5166
+ path: "account",
5167
+ action: "tinycloud.sql/read"
5168
+ };
5169
+ return operations.some((granted) => this.operationCovers(granted, indexRead));
5170
+ }
5022
5171
  async withAccountRegistryRetry(task, signal) {
5023
5172
  const delays = [250, 1e3, 3e3];
5024
5173
  const stopped = (detail) => {
@@ -5485,11 +5634,12 @@ var _TinyCloudNode = class _TinyCloudNode {
5485
5634
  );
5486
5635
  const stagedHost = resolvedHost ?? this.config.host;
5487
5636
  let restoredNodeFeatures;
5637
+ let replicaProtocolMismatch = false;
5488
5638
  if (this.config.replication?.enabled) {
5489
5639
  try {
5490
5640
  restoredNodeFeatures = (await checkNodeInfo2(stagedHost, this.wasmBindings.protocolVersion())).features;
5491
5641
  } catch (error) {
5492
- if (!(error instanceof VersionCheckError)) throw error;
5642
+ replicaProtocolMismatch = error instanceof ProtocolMismatchError;
5493
5643
  }
5494
5644
  }
5495
5645
  const stagedAddress = restoredAddress;
@@ -5497,28 +5647,55 @@ var _TinyCloudNode = class _TinyCloudNode {
5497
5647
  let replicationPrincipal;
5498
5648
  let compactReplicationSession;
5499
5649
  let replicationScopeError;
5650
+ let replicationScopeUnavailable = false;
5500
5651
  if (this.config.replication?.enabled) {
5501
5652
  if (stagedAddress !== void 0) {
5502
5653
  replicationPrincipal = pkhDid2(stagedAddress, stagedChainId);
5503
5654
  } 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 = {
5655
+ try {
5656
+ const { replicationScopeFromSignedSession } = await getNodeReplicationLoaders().authority();
5657
+ const signedScope = replicationScopeFromSignedSession({
5516
5658
  delegationHeader: sessionData.delegationHeader,
5517
5659
  delegationCid: sessionData.delegationCid,
5518
- spaceId: signedScope.space,
5519
5660
  verificationMethod: restoredVerificationMethod,
5520
- jwk: stagedJwk
5521
- };
5661
+ persistedSpace: sessionData.spaceId
5662
+ });
5663
+ if (!signedScope) {
5664
+ replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
5665
+ } else {
5666
+ replicationPrincipal = signedScope.principal;
5667
+ compactReplicationSession = {
5668
+ delegationHeader: sessionData.delegationHeader,
5669
+ delegationCid: sessionData.delegationCid,
5670
+ spaceId: signedScope.space,
5671
+ verificationMethod: restoredVerificationMethod,
5672
+ jwk: stagedJwk
5673
+ };
5674
+ }
5675
+ } catch {
5676
+ replicationScopeUnavailable = true;
5677
+ replicationScopeError = "Replica scope could not be verified for this network session.";
5678
+ }
5679
+ }
5680
+ }
5681
+ let replicaRestoreGate;
5682
+ let restoredReplicaStamp;
5683
+ const replication = this.config.replication;
5684
+ if (replication?.enabled) {
5685
+ if (replicaProtocolMismatch) {
5686
+ replicaRestoreGate = "gate_unavailable";
5687
+ } else if (replicationPrincipal === void 0) {
5688
+ replicaRestoreGate = replicationScopeUnavailable ? "gate_unavailable" : "principal_mismatch";
5689
+ } else {
5690
+ try {
5691
+ const { restoreReplicaSessionStamp } = await getNodeReplicationLoaders().authority();
5692
+ restoredReplicaStamp = await restoreReplicaSessionStamp(
5693
+ replication.storage,
5694
+ replicationPrincipal,
5695
+ sessionData.replicaStamp ?? void 0
5696
+ );
5697
+ } catch (error) {
5698
+ replicaRestoreGate = replicaRestoreRefusal(error);
5522
5699
  }
5523
5700
  }
5524
5701
  }
@@ -5611,7 +5788,10 @@ var _TinyCloudNode = class _TinyCloudNode {
5611
5788
  }
5612
5789
  this.replicationDelegateSession = compactReplicationSession;
5613
5790
  this.replicationScopeError = replicationScopeError;
5614
- if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
5791
+ this.replicaRestoreGate = replicaRestoreGate;
5792
+ this.replicaSessionPrincipal = replicationPrincipal;
5793
+ this.replicaSessionStamp = restoredReplicaStamp;
5794
+ await this.quiesceReplicaRuntime();
5615
5795
  if (epoch !== this.sessionEpoch) {
5616
5796
  oldGraph.retire();
5617
5797
  oldCore?.retireServices?.();
@@ -5626,7 +5806,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5626
5806
  detail: replicationScopeError
5627
5807
  });
5628
5808
  }
5629
- await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
5809
+ await this.bindReplicaOrGate(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
5630
5810
  oldCore?.retireServices?.();
5631
5811
  if (epoch !== this.sessionEpoch) {
5632
5812
  if (attempt === this.latestSessionAttempt) await this.endSession();
@@ -5759,6 +5939,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5759
5939
  session: input.serviceSession,
5760
5940
  invoke: graph.invoke,
5761
5941
  invokeAny: graph.invokeAny,
5942
+ signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
5762
5943
  fetch: graph.fetch
5763
5944
  });
5764
5945
  const keyProvider = new WasmKeyProvider({ sessionManager: input.manager });
@@ -6102,6 +6283,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6102
6283
  signer: this.signer,
6103
6284
  signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
6104
6285
  wasmBindings: this.wasmBindings,
6286
+ ...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
6105
6287
  sessionManager: this.sessionManager,
6106
6288
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
6107
6289
  domain: this.siweDomain,
@@ -6160,6 +6342,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6160
6342
  signer: this.signer,
6161
6343
  signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
6162
6344
  wasmBindings: this.wasmBindings,
6345
+ ...this.config.replication?.enabled === true ? { establishReplicaStamp: this.establishReplicaStamp } : {},
6163
6346
  sessionManager: this.sessionManager,
6164
6347
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
6165
6348
  domain: this.siweDomain,
@@ -6240,7 +6423,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6240
6423
  jwk: session.jwk
6241
6424
  };
6242
6425
  this._serviceContext.setSession(serviceSession);
6243
- await this.bindReplicationRuntime(this._serviceContext, serviceSession, this._kv);
6426
+ await this.bindReplicaOrGate(this._serviceContext, serviceSession, this._kv);
6244
6427
  this.tc.serviceContext.setSession(serviceSession);
6245
6428
  this._vault = this.createVaultService(session.spaceId, this._kv);
6246
6429
  this._vault.initialize(this._serviceContext);
@@ -6634,6 +6817,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6634
6817
  session: serviceSession,
6635
6818
  invoke: graph.invoke,
6636
6819
  invokeAny: graph.invokeAny,
6820
+ signOwnerRevocation: this.signer ? (cid) => this.signOwnerRevocation(cid) : void 0,
6637
6821
  fetch: graph.fetch
6638
6822
  });
6639
6823
  this._spaceService = new SpaceService({
@@ -7926,7 +8110,8 @@ var _TinyCloudNode = class _TinyCloudNode {
7926
8110
  return this.delegationManager.create(params);
7927
8111
  }
7928
8112
  /**
7929
- * Revoke a delegation using the v2 DelegationManager.
8113
+ * Revoke a delegation with the owner wallet when available. Session-only
8114
+ * clients continue to use their delegated revocation authority.
7930
8115
  *
7931
8116
  * @param cid - The CID of the delegation to revoke
7932
8117
  * @returns Result indicating success or failure
@@ -8201,34 +8386,67 @@ var _TinyCloudNode = class _TinyCloudNode {
8201
8386
  return auth.replicationSignInEntries();
8202
8387
  }
8203
8388
  async replicationAuthority() {
8204
- const { createReplicationAuthority } = await getNodeReplicationLoaders().authority();
8389
+ const { createReplicationAuthority, mintNodeReplicationGrant, verifiedOwnerConsentAnchor } = await getNodeReplicationLoaders().authority();
8205
8390
  return createReplicationAuthority({
8206
8391
  replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
8392
+ verifiedConsentAnchor: (session) => {
8393
+ const current = this.currentTinyCloudSession();
8394
+ return session === current && current !== void 0 ? verifiedOwnerConsentAnchor(current, this.wasmBindings, exactVerifiedRecap) : null;
8395
+ },
8207
8396
  siweExpiration: (siwe) => extractSiweExpiration(siwe),
8208
8397
  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
- }
8398
+ mintDelegation: (deviceDid, entries, options) => mintNodeReplicationGrant(
8399
+ deviceDid,
8400
+ entries,
8401
+ options,
8402
+ options?.renewalOnly ? this.currentTinyCloudSession() : void 0,
8403
+ this
8404
+ )
8216
8405
  });
8217
8406
  }
8218
- async bindReplicationRuntime(context, session, kv, principal) {
8407
+ async quiesceReplicaRuntime() {
8219
8408
  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
- });
8409
+ try {
8410
+ await (await this.replicationRuntime).unbind();
8411
+ } catch {
8412
+ this.replicaRestoreGate = "gate_unavailable";
8413
+ }
8414
+ }
8415
+ async bindReplicaOrGate(context, session, kv, principal) {
8416
+ if (!this.replicationRuntime) return;
8417
+ const gate = this.replicaRestoreGate;
8418
+ const epoch = this.sessionEpoch;
8419
+ const attempt = this.latestSessionAttempt;
8420
+ try {
8421
+ const runtime = await this.replicationRuntime;
8422
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context || gate !== this.replicaRestoreGate) return;
8423
+ if (gate) {
8424
+ await runtime.bindGatedNetworkOnly({
8425
+ context,
8426
+ session,
8427
+ gate,
8428
+ primaryKV: [{ space: session.spaceId, kv }]
8429
+ }, getNodeReplicationLoaders().gatedNetwork);
8430
+ return;
8431
+ }
8432
+ const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
8433
+ if (identityPrincipal === void 0) return;
8434
+ const authority = await this.replicationAuthority();
8435
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
8436
+ await runtime.bind({
8437
+ context,
8438
+ session,
8439
+ principal: identityPrincipal,
8440
+ stamp: this.replicaSessionStamp,
8441
+ authority,
8442
+ nodeFeatures: this.replicationNodeFeatures,
8443
+ primaryKV: [{ space: session.spaceId, kv }]
8444
+ });
8445
+ } catch {
8446
+ if (epoch !== this.sessionEpoch || attempt !== this.latestSessionAttempt || this._kv !== kv || this._serviceContext !== context) return;
8447
+ this.replicaRestoreGate = "gate_unavailable";
8448
+ await this.quiesceReplicaRuntime();
8449
+ }
8232
8450
  }
8233
8451
  /** Stable control facade for this node's optional replication runtime. */
8234
8452
  get replication() {
@@ -9436,8 +9654,8 @@ import {
9436
9654
  Space
9437
9655
  } from "@tinycloud/sdk-core";
9438
9656
  import {
9439
- ProtocolMismatchError,
9440
- VersionCheckError as VersionCheckError2,
9657
+ ProtocolMismatchError as ProtocolMismatchError2,
9658
+ VersionCheckError,
9441
9659
  UnsupportedFeatureError as UnsupportedFeatureError2,
9442
9660
  checkNodeInfo as checkNodeInfo3
9443
9661
  } from "@tinycloud/sdk-core";
@@ -9470,7 +9688,7 @@ export {
9470
9688
  NodeUserAuthorization,
9471
9689
  PermissionNotInManifestError2 as PermissionNotInManifestError,
9472
9690
  PrefixedKVService,
9473
- ProtocolMismatchError,
9691
+ ProtocolMismatchError2 as ProtocolMismatchError,
9474
9692
  SECRET_NAME_RE,
9475
9693
  SQLAction,
9476
9694
  SQLService3 as SQLService,
@@ -9490,7 +9708,7 @@ export {
9490
9708
  VAULT_PERMISSION_SERVICE,
9491
9709
  VaultHeaders,
9492
9710
  VaultPublicSpaceKVActions,
9493
- VersionCheckError2 as VersionCheckError,
9711
+ VersionCheckError,
9494
9712
  WasmKeyProvider,
9495
9713
  activateCompactRuntimeDelegation,
9496
9714
  addressStorageKey,
@@ -9519,6 +9737,7 @@ export {
9519
9737
  expandActionShortNames,
9520
9738
  expandPermissionEntries2 as expandPermissionEntries,
9521
9739
  expandPermissionEntry,
9740
+ getNodeReplicationLoaders,
9522
9741
  getTinyCloudDebugLogs,
9523
9742
  installTinyCloudDebugGlobals,
9524
9743
  isCapabilitySubset3 as isCapabilitySubset,
@@ -9532,7 +9751,7 @@ export {
9532
9751
  parseSecretCatalogKey,
9533
9752
  parseSpaceUri2 as parseSpaceUri,
9534
9753
  pkhDid3 as pkhDid,
9535
- principalDid2 as principalDid,
9754
+ principalDid,
9536
9755
  principalDidEquals4 as principalDidEquals,
9537
9756
  registerReplicationLoaders,
9538
9757
  resolveManifest2 as resolveManifest,