@amalgm/shell 0.1.105 → 0.1.106

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.
@@ -3,7 +3,7 @@ import { existsSync, lstatSync, realpathSync, readFileSync, readdirSync, readlin
3
3
  import { open } from "node:fs/promises";
4
4
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { buildUserHomeManifest, buildUserManifest, liveMachineStateDir, scopedAmalgmDir, shippedUserHomeDeclaration, } from "@amalgm/core/identity";
6
- import { CHUNK_BYTES, CONTENT_CONTRACT, INLINE_CARGO_MAX_BYTES, ENTITY_CLOUD_CONTRACT, ENTITY_CLOUD_SCHEMA_VERSION, artifactForRecord, canonicalVersion, checkContentManifest, classifyDirectory, classifyFile, classifyRegisteredRoot, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, downloadAll as downloadAllArtifacts, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, PRESIGN_BATCH_OBJECTS, privateEntityResourceId, repositoryIdentityHash, rootReplacementFromRecords, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } from "@amalgm/live";
6
+ import { CHUNK_BYTES, CONTENT_CONTRACT, INLINE_CARGO_MAX_BYTES, ENTITY_CLOUD_CONTRACT, ENTITY_CLOUD_SCHEMA_VERSION, artifactForRecord, canonicalVersion, checkContentManifest, classifyDirectory, classifyFile, classifyRegisteredRoot, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, decodeAcceptedEntityRecord, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, downloadAll as downloadAllArtifacts, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, PRESIGN_BATCH_OBJECTS, privateEntityResourceId, repositoryIdentityHash, rootReplacementFromRecords, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } from "@amalgm/live";
7
7
  import Database from "better-sqlite3";
8
8
  import { atomicCopy, atomicWrite, ensurePrivateDir, ensureUserDir } from "./filesystem.js";
9
9
  import { emitFilesStage, } from "./files-observability.js";
@@ -25,6 +25,7 @@ import { projectMaterializedGraph } from "./materialized-graph.js";
25
25
  import { NodeWatchHost, } from "./watching/index.js";
26
26
  import { GroundCoordinator } from "./ground-coordination.js";
27
27
  import { submitMutationOperation } from "./paged-mutation-submit.js";
28
+ import { adoptSnapshotDeliveryBaseline } from "./snapshot-delivery-baseline.js";
28
29
  import { WireClient, WireRequestError } from "./wire-client.js";
29
30
  const SMALL_CONTENT_UPLOAD_CONCURRENCY = 16;
30
31
  /** Storage-object lanes for one download set. Tiny objects use the wide
@@ -259,13 +260,31 @@ export class UserGroundHost {
259
260
  const cloud = await this.cloudPort(identity).lookup(resourceId);
260
261
  if (!cloud)
261
262
  throw new Error("this user has no cloud entity registry");
263
+ // The authority's active snapshot is the compact base; Add's active
264
+ // materialization head is that snapshot plus the complete committed
265
+ // tail this machine has now received. Fold the tail before Live
266
+ // selects the workspace so a destination created after mutations is
267
+ // exact immediately, rather than first installing stale ground and
268
+ // racing Apply afterward.
269
+ await this.recordRail?.flush();
270
+ const deliveryWatermark = readAuthorityDeliveryCursor(this.databasePath(identity), resourceId);
271
+ if (deliveryWatermark < cloud.deliveryWatermark) {
272
+ throw new Error("local delivery cursor is behind the active snapshot watermark");
273
+ }
274
+ const snapshot = snapshotFromRecords(recordsAtDeliveryPrefix(cloud.snapshot.records, readAcceptedResults(this.databasePath(identity), resourceId, cloud.deliveryWatermark, deliveryWatermark)));
275
+ const current = {
276
+ ...cloud,
277
+ deliveryWatermark,
278
+ checksum: sha256Hex(stableJson(snapshot)),
279
+ snapshot,
280
+ };
262
281
  this.stage({
263
282
  primitive: "download",
264
283
  stage: "registry-lookup",
265
284
  status: "completed",
266
285
  durationMs: performance.now() - started,
267
286
  });
268
- return cloud;
287
+ return current;
269
288
  },
270
289
  materializeWorkspace: async (input) => {
271
290
  const groundWaitStarted = performance.now();
@@ -298,9 +317,10 @@ export class UserGroundHost {
298
317
  records: input.records,
299
318
  destinationParent: input.destinationParent,
300
319
  cloudHead: input.cloudHead,
320
+ deliveryWatermark: input.deliveryWatermark,
301
321
  readContent: (artifacts) => this.downloadContentSet(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: input.workspace.uuid }),
302
322
  adoptMaterialization: (rootUUID) => {
303
- const settled = this.applyHost?.adoptVerifiedMaterialization(rootUUID) ?? 0;
323
+ const settled = this.adoptCloudDeliveryBaseline(identity, input.deliveryWatermark, rootUUID);
304
324
  this.applyRail?.recordsAvailable();
305
325
  if (settled > 0) {
306
326
  this.stage({
@@ -424,9 +444,10 @@ export class UserGroundHost {
424
444
  records: intent.records,
425
445
  destinationParent: dirname(intent.destinationPath),
426
446
  cloudHead: intent.cloudHead,
447
+ deliveryWatermark: intent.deliveryWatermark,
427
448
  readContent: (artifacts) => this.downloadContentSet(intent.resourceId, this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: intent.workspaceId }),
428
449
  adoptMaterialization: (rootUUID) => {
429
- this.applyHost?.adoptVerifiedMaterialization(rootUUID);
450
+ this.adoptCloudDeliveryBaseline(identity, intent.deliveryWatermark, rootUUID);
430
451
  this.applyRail?.recordsAvailable();
431
452
  },
432
453
  onStage: (stage) => this.options.onWorkspaceAddStage?.({
@@ -504,7 +525,8 @@ export class UserGroundHost {
504
525
  // the publish path re-proves seal evidence and repairs any missing
505
526
  // uploads before this answer is lawful.
506
527
  try {
507
- await this.publishMaterializedSnapshot(identity, new Set([workspaceId]), { journey: "register", workspaceId });
528
+ const deliveryWatermark = await this.prepareSnapshotPublication(identity, { journey: "register", workspaceId });
529
+ await this.publishMaterializedSnapshot(identity, new Set([workspaceId]), deliveryWatermark, { journey: "register", workspaceId });
508
530
  }
509
531
  finally {
510
532
  this.finishColdOperation();
@@ -519,6 +541,10 @@ export class UserGroundHost {
519
541
  // Register is a cold declaration, not a filesystem edit. It performs one
520
542
  // complete reconciliation and publishes one graph head; it must not mint
521
543
  // a parallel stream of Detect records for the same initial contents.
544
+ // Freeze the official prefix before taking ground ownership. Apply may
545
+ // need that same ground, so draining it inside the registration scope
546
+ // would make the two rails wait on one another.
547
+ const deliveryWatermark = await this.prepareSnapshotPublication(identity, { journey: "register", workspaceId });
522
548
  let releaseGround = null;
523
549
  try {
524
550
  const groundWaitStarted = performance.now();
@@ -612,7 +638,7 @@ export class UserGroundHost {
612
638
  if (!scanned.publicationRootIds) {
613
639
  throw new Error("cold registration reconciliation did not name its publication roots");
614
640
  }
615
- await this.publishMaterializedSnapshot(identity, new Set(scanned.publicationRootIds), { journey: "register", workspaceId });
641
+ await this.publishMaterializedSnapshot(identity, new Set(scanned.publicationRootIds), deliveryWatermark, { journey: "register", workspaceId });
616
642
  this.watchHost.settle(observations);
617
643
  }
618
644
  catch (error) {
@@ -710,6 +736,20 @@ export class UserGroundHost {
710
736
  if (this.syncing)
711
737
  await this.syncing;
712
738
  }
739
+ /** Install the exact snapshot/tail boundary before Receive or Apply can
740
+ * treat older official history as materialization work. The SQLite cursor,
741
+ * retained Inbox resolution, and Apply baseline all derive from the same N. */
742
+ adoptCloudDeliveryBaseline(identity, deliveryWatermark, rootUUID = null) {
743
+ const database = initializeDatabase(this.databasePath(identity));
744
+ let resolved = 0;
745
+ try {
746
+ resolved = adoptSnapshotDeliveryBaseline(database, privateEntityResourceId(identity.userId, sha256Hex), deliveryWatermark, this.options.now?.().getTime() ?? Date.now());
747
+ }
748
+ finally {
749
+ database.close();
750
+ }
751
+ return resolved + (this.applyHost?.adoptVerifiedMaterialization(rootUUID) ?? 0);
752
+ }
713
753
  async startRecordRail(identity) {
714
754
  if (!this.cloudState)
715
755
  throw new Error("entity Record rail requires cloud authority");
@@ -717,12 +757,13 @@ export class UserGroundHost {
717
757
  throw new Error("entity Record rail already started");
718
758
  await this.wire.open();
719
759
  // One floor, no per-feature gates: this shell and its gateway release
720
- // together, and the gateway ships first. 10 = contract-aware cargo
721
- // closure; an older gateway drops the sealed-evidence frame the upload
722
- // planner depends on, so nothing this shell registers could publish.
723
- if (this.wire.protocolVersion < 10) {
760
+ // together, and the gateway ships first. 11 = snapshot delivery
761
+ // watermarks; an older gateway cannot atomically bind Add's graph to the
762
+ // official entity-record prefix it already includes.
763
+ if (this.wire.protocolVersion < 11) {
724
764
  throw new Error("gateway speaks an older wire protocol than this shell");
725
765
  }
766
+ this.adoptCloudDeliveryBaseline(identity, this.cloudState.deliveryWatermark);
726
767
  const store = new EntityRecordSqliteStore(initializeDatabase(this.databasePath(identity)));
727
768
  const wireAuthority = createEntityRecordAuthorityPort({
728
769
  request: (frame, acceptedTypes, binaryBody) => binaryBody === undefined
@@ -1760,6 +1801,7 @@ export class UserGroundHost {
1760
1801
  authorityEpoch: first.authorityEpoch,
1761
1802
  headVersion: first.baseVersion,
1762
1803
  checksum: "",
1804
+ deliveryWatermark: first.deliveryWatermark,
1763
1805
  records: snapshotFromRecords(JSON.parse(first.snapshotJson).records || []).records,
1764
1806
  };
1765
1807
  try {
@@ -1778,13 +1820,10 @@ export class UserGroundHost {
1778
1820
  /** Register is the deliberate cold graph-publication boundary. Ordinary
1779
1821
  * Watch detection stops at a durable exact record and never rebuilds this
1780
1822
  * resource snapshot. */
1781
- async publishMaterializedSnapshot(identity, publicationRootIds, trace) {
1782
- // Any durable pending intent resolves before a new snapshot derives: an
1783
- // acknowledged publication advances the head through its original
1784
- // mutation id (the authority answers a known mutation id idempotently),
1785
- // and a conclusive refusal marks the row rejected so the snapshot
1786
- // derived below may supersede it. A transport failure keeps the row
1787
- // pending — its outcome is unknown, so nothing may replace it.
1823
+ async prepareSnapshotPublication(identity, trace) {
1824
+ // An older durable publication resolves before the new boundary derives.
1825
+ // Unknown outcomes remain pending; a conclusive refusal may be replaced
1826
+ // by the newer complete snapshot below.
1788
1827
  try {
1789
1828
  await this.publishPendingSnapshots(identity, trace);
1790
1829
  }
@@ -1792,13 +1831,30 @@ export class UserGroundHost {
1792
1831
  if (!conclusivelyRefused(error))
1793
1832
  throw error;
1794
1833
  }
1834
+ await this.recordRail?.flush();
1835
+ const state = this.cloudState;
1836
+ if (!state)
1837
+ throw new Error("cloud state is unavailable for graph publication");
1838
+ // Freeze N immediately after Receive reaches its explicit boundary. Apply
1839
+ // then settles every Inbox row already known at that boundary. A later
1840
+ // delivery may run concurrently, but the graph still declares the older
1841
+ // N and the authority refuses it if that newer record commits first.
1842
+ const deliveryWatermark = readAuthorityDeliveryCursor(this.databasePath(identity), state.resourceId);
1843
+ if (deliveryWatermark < state.deliveryWatermark) {
1844
+ throw new Error("local delivery cursor is behind the active snapshot watermark");
1845
+ }
1846
+ await this.applyRail?.flush();
1847
+ return deliveryWatermark;
1848
+ }
1849
+ async publishMaterializedSnapshot(identity, publicationRootIds, deliveryWatermark, trace) {
1795
1850
  const state = this.cloudState;
1796
1851
  if (!state)
1797
1852
  throw new Error("cloud state is unavailable for graph publication");
1853
+ const officialRecords = recordsAtDeliveryPrefix(state.records, readAcceptedResults(this.databasePath(identity), state.resourceId, state.deliveryWatermark, deliveryWatermark));
1798
1854
  const localRecords = recordsRetainingMissingEvidence(readGroundRowsForRoots(this.databasePath(identity), "entities", publicationRootIds)
1799
1855
  .map(portableRecord), readGroundRowsForRoots(this.databasePath(identity), "detection_notebook", publicationRootIds)
1800
1856
  .map(portableRecord));
1801
- const merged = mergeMaterializedRoots(state.records, localRecords);
1857
+ const merged = mergeMaterializedRoots(officialRecords, localRecords);
1802
1858
  const traveling = travelingRecords(merged, sha256Hex);
1803
1859
  const impossibleGitLeaf = traveling.find((record) => (record.type === "file.text" || record.type === "file.binary" || record.type === "link")
1804
1860
  && record.payloadVersion?.startsWith("git:"));
@@ -1826,7 +1882,7 @@ export class UserGroundHost {
1826
1882
  const uploadRecords = cargo
1827
1883
  .filter((entry) => !sealedArtifacts.has(entry.contentHash))
1828
1884
  .map((entry) => entry.record);
1829
- if (checksum === state.checksum) {
1885
+ if (checksum === state.checksum && deliveryWatermark === state.deliveryWatermark) {
1830
1886
  // The graph is already committed; only cargo can be missing. Repair
1831
1887
  // uploads need no journal row — a crash simply re-runs the same
1832
1888
  // repair on the next Register.
@@ -1857,6 +1913,7 @@ export class UserGroundHost {
1857
1913
  baseVersion: state.headVersion,
1858
1914
  snapshotChecksum: checksum,
1859
1915
  snapshotJson,
1916
+ deliveryWatermark,
1860
1917
  uploadRecords,
1861
1918
  replacementRootIds: replacement.rootIds,
1862
1919
  replacementRecords: replacement.records,
@@ -1923,6 +1980,7 @@ export class UserGroundHost {
1923
1980
  contract: ENTITY_CLOUD_CONTRACT,
1924
1981
  schemaVersion: ENTITY_CLOUD_SCHEMA_VERSION,
1925
1982
  operationKind: replacement.kind,
1983
+ deliveryWatermark: pending.deliveryWatermark,
1926
1984
  }, replacement);
1927
1985
  }
1928
1986
  catch (error) {
@@ -1959,6 +2017,7 @@ export class UserGroundHost {
1959
2017
  authorityEpoch: Number(frame.authority_epoch),
1960
2018
  headVersion: Number(frame.version),
1961
2019
  checksum: pending.snapshotChecksum,
2020
+ deliveryWatermark: Number(frame.delivery_watermark),
1962
2021
  records: snapshot.records,
1963
2022
  };
1964
2023
  deleteJournalEntry(this.databasePath(identity), pending.mutationId);
@@ -2147,6 +2206,10 @@ function initializeDatabase(file) {
2147
2206
  authority_id TEXT PRIMARY KEY,
2148
2207
  received_through INTEGER NOT NULL CHECK(received_through >= 0)
2149
2208
  );
2209
+ CREATE TABLE IF NOT EXISTS authority_snapshot_baselines (
2210
+ authority_id TEXT PRIMARY KEY,
2211
+ delivery_sequence INTEGER NOT NULL CHECK(delivery_sequence >= 0)
2212
+ );
2150
2213
  CREATE TABLE IF NOT EXISTS snapshot_publications (
2151
2214
  sequence INTEGER PRIMARY KEY AUTOINCREMENT,
2152
2215
  mutation_id TEXT NOT NULL UNIQUE,
@@ -2456,9 +2519,13 @@ function parseWorkspaceAddIntent(json) {
2456
2519
  const cloudHead = String(value.cloudHead || "");
2457
2520
  const destinationPath = String(value.destinationPath || "");
2458
2521
  const stagingPath = String(value.stagingPath || "");
2522
+ const deliveryWatermark = Number(value.deliveryWatermark);
2459
2523
  if (!UUID.test(workspaceId) || !resourceId || !/^[0-9a-f]{64}$/i.test(cloudHead)) {
2460
2524
  throw new Error("workspace Add intent identity is invalid");
2461
2525
  }
2526
+ if (!Number.isSafeInteger(deliveryWatermark) || deliveryWatermark < 0) {
2527
+ throw new Error(`workspace Add intent ${workspaceId} has no delivery watermark`);
2528
+ }
2462
2529
  if (!isAbsolute(destinationPath)
2463
2530
  || stagingPath !== workspaceAddStagingPath(destinationPath, workspaceId)) {
2464
2531
  throw new Error(`workspace Add intent ${workspaceId} has invalid placement`);
@@ -2467,6 +2534,7 @@ function parseWorkspaceAddIntent(json) {
2467
2534
  workspaceId,
2468
2535
  resourceId,
2469
2536
  cloudHead: cloudHead.toLowerCase(),
2537
+ deliveryWatermark,
2470
2538
  destinationPath,
2471
2539
  stagingPath,
2472
2540
  records: snapshotFromRecords(Array.isArray(value.records) ? value.records : []).records,
@@ -2578,11 +2646,16 @@ function readSnapshotPublications(file) {
2578
2646
  || !Array.isArray(publication.replacementRecords)) {
2579
2647
  throw new Error(`snapshot publication ${row.mutationId} has no exact root replacement`);
2580
2648
  }
2649
+ const deliveryWatermark = Number(publication.deliveryWatermark);
2650
+ if (!Number.isSafeInteger(deliveryWatermark) || deliveryWatermark < 0) {
2651
+ throw new Error(`snapshot publication ${row.mutationId} has no delivery watermark`);
2652
+ }
2581
2653
  return {
2582
2654
  mutationId: row.mutationId,
2583
2655
  resourceId: row.resourceId,
2584
2656
  authorityEpoch: Number(publication.authorityEpoch),
2585
2657
  baseVersion: Number(publication.baseVersion),
2658
+ deliveryWatermark,
2586
2659
  snapshotChecksum: String(publication.snapshotChecksum),
2587
2660
  snapshotJson: String(publication.snapshotJson),
2588
2661
  uploadRecords: snapshotFromRecords(publication.uploadRecords).records,
@@ -2609,6 +2682,55 @@ function hasRecordsBeyondSnapshot(file) {
2609
2682
  database.close();
2610
2683
  }
2611
2684
  }
2685
+ function readAuthorityDeliveryCursor(file, authorityId) {
2686
+ const database = initializeDatabase(file);
2687
+ try {
2688
+ const row = database.prepare(`
2689
+ SELECT received_through AS receivedThrough
2690
+ FROM authority_delivery_cursors WHERE authority_id = ?
2691
+ `).get(authorityId);
2692
+ return row?.receivedThrough ?? 0;
2693
+ }
2694
+ finally {
2695
+ database.close();
2696
+ }
2697
+ }
2698
+ /** Latest official result for every UUID in the newly received complete
2699
+ * authority prefix. The record carries the whole logical result, so roots
2700
+ * this machine has not materialized still advance honestly in a Register
2701
+ * snapshot without inventing filesystem evidence. */
2702
+ function readAcceptedResults(file, authorityId, afterDelivery, throughDelivery) {
2703
+ if (throughDelivery <= afterDelivery)
2704
+ return [];
2705
+ const database = initializeDatabase(file);
2706
+ try {
2707
+ const latest = new Map();
2708
+ for (const { recordJson } of database.prepare(`
2709
+ SELECT record_json AS recordJson FROM record_inbox
2710
+ WHERE authority_id = ?
2711
+ AND delivery_sequence > ? AND delivery_sequence <= ?
2712
+ ORDER BY delivery_sequence
2713
+ `).iterate(authorityId, afterDelivery, throughDelivery)) {
2714
+ const accepted = decodeAcceptedEntityRecord(recordJson);
2715
+ latest.set(accepted.entityId, accepted.change.result);
2716
+ }
2717
+ return [...latest.values()];
2718
+ }
2719
+ finally {
2720
+ database.close();
2721
+ }
2722
+ }
2723
+ function recordsAtDeliveryPrefix(base, results) {
2724
+ if (results.length === 0)
2725
+ return base;
2726
+ const records = new Map(base.map((record) => [record.uuid, record]));
2727
+ for (const result of results)
2728
+ records.set(result.uuid, result);
2729
+ // Entity channels are independently ordered. Container membership is the
2730
+ // one derived relationship, so derive it once from the final prefix rather
2731
+ // than letting record arrival order choose a container version.
2732
+ return recordsRetainingMissingEvidence([...records.values()], []);
2733
+ }
2612
2734
  /** Record is the SQLite transaction that accepts Detect's exact proposals and
2613
2735
  * advances the last-verified notebook. Watch may settle only after this
2614
2736
  * function returns. The record table is the local-only outbox. */
@@ -2711,6 +2833,7 @@ function supersedeSnapshotJournal(file, entry) {
2711
2833
  `).run(entry.mutationId, entry.resourceId, stableJson({
2712
2834
  authorityEpoch: entry.authorityEpoch,
2713
2835
  baseVersion: entry.baseVersion,
2836
+ deliveryWatermark: entry.deliveryWatermark,
2714
2837
  snapshotChecksum: entry.snapshotChecksum,
2715
2838
  snapshotJson: entry.snapshotJson,
2716
2839
  uploadRecords: entry.uploadRecords,
@@ -4600,7 +4723,7 @@ function installCloudWorkspaceReferences(input) {
4600
4723
  return references;
4601
4724
  }
4602
4725
  async function installCloudWorkspace(input) {
4603
- const { identity, userRoot, database, cacheDir, bindingDir, resourceId, workspace, reference, references, records, destinationParent, cloudHead, readContent, onStage, } = input;
4726
+ const { identity, userRoot, database, cacheDir, bindingDir, resourceId, workspace, reference, references, records, destinationParent, cloudHead, deliveryWatermark, readContent, onStage, } = input;
4604
4727
  const allLocalRows = readRows(database);
4605
4728
  const existingBoundary = allLocalRows.find((row) => row.uuid === workspace.uuid);
4606
4729
  const existing = existingBoundary
@@ -4654,6 +4777,7 @@ async function installCloudWorkspace(input) {
4654
4777
  workspace: activeSelection.workspace,
4655
4778
  references: activeReferences,
4656
4779
  });
4780
+ input.adoptMaterialization(workspace.uuid);
4657
4781
  return {
4658
4782
  path: root.absolutePath,
4659
4783
  rows: existing.length,
@@ -4707,6 +4831,7 @@ async function installCloudWorkspace(input) {
4707
4831
  workspaceId: workspace.uuid,
4708
4832
  resourceId,
4709
4833
  cloudHead,
4834
+ deliveryWatermark,
4710
4835
  destinationPath: destination,
4711
4836
  stagingPath,
4712
4837
  records: snapshotFromRecords(records).records,
@@ -4785,6 +4910,7 @@ function readyFromFrame(frame) {
4785
4910
  const cloud = {
4786
4911
  resourceId: String(frame.resource_id || ""),
4787
4912
  version: Number(frame.head_version),
4913
+ deliveryWatermark: Number(frame.delivery_watermark),
4788
4914
  checksum: String(frame.snapshot_checksum || ""),
4789
4915
  snapshot: parseSnapshot(bytes),
4790
4916
  };
@@ -4795,6 +4921,7 @@ function readyFromFrame(frame) {
4795
4921
  authorityEpoch: Number(frame.authority_epoch),
4796
4922
  headVersion: cloud.version,
4797
4923
  checksum: cloud.checksum,
4924
+ deliveryWatermark: cloud.deliveryWatermark,
4798
4925
  records: cloud.snapshot.records,
4799
4926
  },
4800
4927
  };