@amalgm/shell 0.1.104 → 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,14 +25,14 @@ 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
- /** Storage-object lanes for one download set. Manifests and blocks are all
31
- * independent content-addressed objects fetched straight from storage, so
32
- * twelve thousand small files and one large pack share the same wide bound;
33
- * direct fetches cost the gateway nothing, and memory stays bounded by
34
- * lanes × one block. */
31
+ /** Storage-object lanes for one download set. Tiny objects use the wide
32
+ * request window, while the separate byte budget narrows large chunks so
33
+ * request concurrency can never silently become an unbounded memory claim. */
35
34
  const DIRECT_OBJECT_DOWNLOAD_CONCURRENCY = 128;
35
+ const DIRECT_OBJECT_DOWNLOAD_MAX_IN_FLIGHT_BYTES = 8 * CHUNK_BYTES;
36
36
  const DETECT_QUIET_MS = 12;
37
37
  const DETECT_MAX_DEFERRAL_MS = 75;
38
38
  const DETECT_RETRY_MS = 250;
@@ -260,13 +260,31 @@ export class UserGroundHost {
260
260
  const cloud = await this.cloudPort(identity).lookup(resourceId);
261
261
  if (!cloud)
262
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
+ };
263
281
  this.stage({
264
282
  primitive: "download",
265
283
  stage: "registry-lookup",
266
284
  status: "completed",
267
285
  durationMs: performance.now() - started,
268
286
  });
269
- return cloud;
287
+ return current;
270
288
  },
271
289
  materializeWorkspace: async (input) => {
272
290
  const groundWaitStarted = performance.now();
@@ -299,9 +317,10 @@ export class UserGroundHost {
299
317
  records: input.records,
300
318
  destinationParent: input.destinationParent,
301
319
  cloudHead: input.cloudHead,
320
+ deliveryWatermark: input.deliveryWatermark,
302
321
  readContent: (artifacts) => this.downloadContentSet(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: input.workspace.uuid }),
303
322
  adoptMaterialization: (rootUUID) => {
304
- const settled = this.applyHost?.adoptVerifiedMaterialization(rootUUID) ?? 0;
323
+ const settled = this.adoptCloudDeliveryBaseline(identity, input.deliveryWatermark, rootUUID);
305
324
  this.applyRail?.recordsAvailable();
306
325
  if (settled > 0) {
307
326
  this.stage({
@@ -425,9 +444,10 @@ export class UserGroundHost {
425
444
  records: intent.records,
426
445
  destinationParent: dirname(intent.destinationPath),
427
446
  cloudHead: intent.cloudHead,
447
+ deliveryWatermark: intent.deliveryWatermark,
428
448
  readContent: (artifacts) => this.downloadContentSet(intent.resourceId, this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: intent.workspaceId }),
429
449
  adoptMaterialization: (rootUUID) => {
430
- this.applyHost?.adoptVerifiedMaterialization(rootUUID);
450
+ this.adoptCloudDeliveryBaseline(identity, intent.deliveryWatermark, rootUUID);
431
451
  this.applyRail?.recordsAvailable();
432
452
  },
433
453
  onStage: (stage) => this.options.onWorkspaceAddStage?.({
@@ -505,7 +525,8 @@ export class UserGroundHost {
505
525
  // the publish path re-proves seal evidence and repairs any missing
506
526
  // uploads before this answer is lawful.
507
527
  try {
508
- 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 });
509
530
  }
510
531
  finally {
511
532
  this.finishColdOperation();
@@ -520,6 +541,10 @@ export class UserGroundHost {
520
541
  // Register is a cold declaration, not a filesystem edit. It performs one
521
542
  // complete reconciliation and publishes one graph head; it must not mint
522
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 });
523
548
  let releaseGround = null;
524
549
  try {
525
550
  const groundWaitStarted = performance.now();
@@ -613,7 +638,7 @@ export class UserGroundHost {
613
638
  if (!scanned.publicationRootIds) {
614
639
  throw new Error("cold registration reconciliation did not name its publication roots");
615
640
  }
616
- await this.publishMaterializedSnapshot(identity, new Set(scanned.publicationRootIds), { journey: "register", workspaceId });
641
+ await this.publishMaterializedSnapshot(identity, new Set(scanned.publicationRootIds), deliveryWatermark, { journey: "register", workspaceId });
617
642
  this.watchHost.settle(observations);
618
643
  }
619
644
  catch (error) {
@@ -711,6 +736,20 @@ export class UserGroundHost {
711
736
  if (this.syncing)
712
737
  await this.syncing;
713
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
+ }
714
753
  async startRecordRail(identity) {
715
754
  if (!this.cloudState)
716
755
  throw new Error("entity Record rail requires cloud authority");
@@ -718,12 +757,13 @@ export class UserGroundHost {
718
757
  throw new Error("entity Record rail already started");
719
758
  await this.wire.open();
720
759
  // One floor, no per-feature gates: this shell and its gateway release
721
- // together, and the gateway ships first. 10 = contract-aware cargo
722
- // closure; an older gateway drops the sealed-evidence frame the upload
723
- // planner depends on, so nothing this shell registers could publish.
724
- 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) {
725
764
  throw new Error("gateway speaks an older wire protocol than this shell");
726
765
  }
766
+ this.adoptCloudDeliveryBaseline(identity, this.cloudState.deliveryWatermark);
727
767
  const store = new EntityRecordSqliteStore(initializeDatabase(this.databasePath(identity)));
728
768
  const wireAuthority = createEntityRecordAuthorityPort({
729
769
  request: (frame, acceptedTypes, binaryBody) => binaryBody === undefined
@@ -1362,7 +1402,10 @@ export class UserGroundHost {
1362
1402
  const sealed = await target(artifact).seal();
1363
1403
  return { local: sealed, bytes: sealed.bytes, contentHash: sealed.contentHash };
1364
1404
  },
1365
- }, { concurrency: DIRECT_OBJECT_DOWNLOAD_CONCURRENCY });
1405
+ }, {
1406
+ concurrency: DIRECT_OBJECT_DOWNLOAD_CONCURRENCY,
1407
+ maxInFlightBytes: DIRECT_OBJECT_DOWNLOAD_MAX_IN_FLIGHT_BYTES,
1408
+ });
1366
1409
  const content = new Map();
1367
1410
  for (const receipt of receipts) {
1368
1411
  declaredBytes += receipt.manifest.bytes;
@@ -1758,6 +1801,7 @@ export class UserGroundHost {
1758
1801
  authorityEpoch: first.authorityEpoch,
1759
1802
  headVersion: first.baseVersion,
1760
1803
  checksum: "",
1804
+ deliveryWatermark: first.deliveryWatermark,
1761
1805
  records: snapshotFromRecords(JSON.parse(first.snapshotJson).records || []).records,
1762
1806
  };
1763
1807
  try {
@@ -1776,13 +1820,10 @@ export class UserGroundHost {
1776
1820
  /** Register is the deliberate cold graph-publication boundary. Ordinary
1777
1821
  * Watch detection stops at a durable exact record and never rebuilds this
1778
1822
  * resource snapshot. */
1779
- async publishMaterializedSnapshot(identity, publicationRootIds, trace) {
1780
- // Any durable pending intent resolves before a new snapshot derives: an
1781
- // acknowledged publication advances the head through its original
1782
- // mutation id (the authority answers a known mutation id idempotently),
1783
- // and a conclusive refusal marks the row rejected so the snapshot
1784
- // derived below may supersede it. A transport failure keeps the row
1785
- // 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.
1786
1827
  try {
1787
1828
  await this.publishPendingSnapshots(identity, trace);
1788
1829
  }
@@ -1790,13 +1831,30 @@ export class UserGroundHost {
1790
1831
  if (!conclusivelyRefused(error))
1791
1832
  throw error;
1792
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) {
1793
1850
  const state = this.cloudState;
1794
1851
  if (!state)
1795
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));
1796
1854
  const localRecords = recordsRetainingMissingEvidence(readGroundRowsForRoots(this.databasePath(identity), "entities", publicationRootIds)
1797
1855
  .map(portableRecord), readGroundRowsForRoots(this.databasePath(identity), "detection_notebook", publicationRootIds)
1798
1856
  .map(portableRecord));
1799
- const merged = mergeMaterializedRoots(state.records, localRecords);
1857
+ const merged = mergeMaterializedRoots(officialRecords, localRecords);
1800
1858
  const traveling = travelingRecords(merged, sha256Hex);
1801
1859
  const impossibleGitLeaf = traveling.find((record) => (record.type === "file.text" || record.type === "file.binary" || record.type === "link")
1802
1860
  && record.payloadVersion?.startsWith("git:"));
@@ -1824,7 +1882,7 @@ export class UserGroundHost {
1824
1882
  const uploadRecords = cargo
1825
1883
  .filter((entry) => !sealedArtifacts.has(entry.contentHash))
1826
1884
  .map((entry) => entry.record);
1827
- if (checksum === state.checksum) {
1885
+ if (checksum === state.checksum && deliveryWatermark === state.deliveryWatermark) {
1828
1886
  // The graph is already committed; only cargo can be missing. Repair
1829
1887
  // uploads need no journal row — a crash simply re-runs the same
1830
1888
  // repair on the next Register.
@@ -1855,6 +1913,7 @@ export class UserGroundHost {
1855
1913
  baseVersion: state.headVersion,
1856
1914
  snapshotChecksum: checksum,
1857
1915
  snapshotJson,
1916
+ deliveryWatermark,
1858
1917
  uploadRecords,
1859
1918
  replacementRootIds: replacement.rootIds,
1860
1919
  replacementRecords: replacement.records,
@@ -1921,6 +1980,7 @@ export class UserGroundHost {
1921
1980
  contract: ENTITY_CLOUD_CONTRACT,
1922
1981
  schemaVersion: ENTITY_CLOUD_SCHEMA_VERSION,
1923
1982
  operationKind: replacement.kind,
1983
+ deliveryWatermark: pending.deliveryWatermark,
1924
1984
  }, replacement);
1925
1985
  }
1926
1986
  catch (error) {
@@ -1957,6 +2017,7 @@ export class UserGroundHost {
1957
2017
  authorityEpoch: Number(frame.authority_epoch),
1958
2018
  headVersion: Number(frame.version),
1959
2019
  checksum: pending.snapshotChecksum,
2020
+ deliveryWatermark: Number(frame.delivery_watermark),
1960
2021
  records: snapshot.records,
1961
2022
  };
1962
2023
  deleteJournalEntry(this.databasePath(identity), pending.mutationId);
@@ -2145,6 +2206,10 @@ function initializeDatabase(file) {
2145
2206
  authority_id TEXT PRIMARY KEY,
2146
2207
  received_through INTEGER NOT NULL CHECK(received_through >= 0)
2147
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
+ );
2148
2213
  CREATE TABLE IF NOT EXISTS snapshot_publications (
2149
2214
  sequence INTEGER PRIMARY KEY AUTOINCREMENT,
2150
2215
  mutation_id TEXT NOT NULL UNIQUE,
@@ -2454,9 +2519,13 @@ function parseWorkspaceAddIntent(json) {
2454
2519
  const cloudHead = String(value.cloudHead || "");
2455
2520
  const destinationPath = String(value.destinationPath || "");
2456
2521
  const stagingPath = String(value.stagingPath || "");
2522
+ const deliveryWatermark = Number(value.deliveryWatermark);
2457
2523
  if (!UUID.test(workspaceId) || !resourceId || !/^[0-9a-f]{64}$/i.test(cloudHead)) {
2458
2524
  throw new Error("workspace Add intent identity is invalid");
2459
2525
  }
2526
+ if (!Number.isSafeInteger(deliveryWatermark) || deliveryWatermark < 0) {
2527
+ throw new Error(`workspace Add intent ${workspaceId} has no delivery watermark`);
2528
+ }
2460
2529
  if (!isAbsolute(destinationPath)
2461
2530
  || stagingPath !== workspaceAddStagingPath(destinationPath, workspaceId)) {
2462
2531
  throw new Error(`workspace Add intent ${workspaceId} has invalid placement`);
@@ -2465,6 +2534,7 @@ function parseWorkspaceAddIntent(json) {
2465
2534
  workspaceId,
2466
2535
  resourceId,
2467
2536
  cloudHead: cloudHead.toLowerCase(),
2537
+ deliveryWatermark,
2468
2538
  destinationPath,
2469
2539
  stagingPath,
2470
2540
  records: snapshotFromRecords(Array.isArray(value.records) ? value.records : []).records,
@@ -2576,11 +2646,16 @@ function readSnapshotPublications(file) {
2576
2646
  || !Array.isArray(publication.replacementRecords)) {
2577
2647
  throw new Error(`snapshot publication ${row.mutationId} has no exact root replacement`);
2578
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
+ }
2579
2653
  return {
2580
2654
  mutationId: row.mutationId,
2581
2655
  resourceId: row.resourceId,
2582
2656
  authorityEpoch: Number(publication.authorityEpoch),
2583
2657
  baseVersion: Number(publication.baseVersion),
2658
+ deliveryWatermark,
2584
2659
  snapshotChecksum: String(publication.snapshotChecksum),
2585
2660
  snapshotJson: String(publication.snapshotJson),
2586
2661
  uploadRecords: snapshotFromRecords(publication.uploadRecords).records,
@@ -2607,6 +2682,55 @@ function hasRecordsBeyondSnapshot(file) {
2607
2682
  database.close();
2608
2683
  }
2609
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
+ }
2610
2734
  /** Record is the SQLite transaction that accepts Detect's exact proposals and
2611
2735
  * advances the last-verified notebook. Watch may settle only after this
2612
2736
  * function returns. The record table is the local-only outbox. */
@@ -2709,6 +2833,7 @@ function supersedeSnapshotJournal(file, entry) {
2709
2833
  `).run(entry.mutationId, entry.resourceId, stableJson({
2710
2834
  authorityEpoch: entry.authorityEpoch,
2711
2835
  baseVersion: entry.baseVersion,
2836
+ deliveryWatermark: entry.deliveryWatermark,
2712
2837
  snapshotChecksum: entry.snapshotChecksum,
2713
2838
  snapshotJson: entry.snapshotJson,
2714
2839
  uploadRecords: entry.uploadRecords,
@@ -4598,7 +4723,7 @@ function installCloudWorkspaceReferences(input) {
4598
4723
  return references;
4599
4724
  }
4600
4725
  async function installCloudWorkspace(input) {
4601
- 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;
4602
4727
  const allLocalRows = readRows(database);
4603
4728
  const existingBoundary = allLocalRows.find((row) => row.uuid === workspace.uuid);
4604
4729
  const existing = existingBoundary
@@ -4652,6 +4777,7 @@ async function installCloudWorkspace(input) {
4652
4777
  workspace: activeSelection.workspace,
4653
4778
  references: activeReferences,
4654
4779
  });
4780
+ input.adoptMaterialization(workspace.uuid);
4655
4781
  return {
4656
4782
  path: root.absolutePath,
4657
4783
  rows: existing.length,
@@ -4705,6 +4831,7 @@ async function installCloudWorkspace(input) {
4705
4831
  workspaceId: workspace.uuid,
4706
4832
  resourceId,
4707
4833
  cloudHead,
4834
+ deliveryWatermark,
4708
4835
  destinationPath: destination,
4709
4836
  stagingPath,
4710
4837
  records: snapshotFromRecords(records).records,
@@ -4783,6 +4910,7 @@ function readyFromFrame(frame) {
4783
4910
  const cloud = {
4784
4911
  resourceId: String(frame.resource_id || ""),
4785
4912
  version: Number(frame.head_version),
4913
+ deliveryWatermark: Number(frame.delivery_watermark),
4786
4914
  checksum: String(frame.snapshot_checksum || ""),
4787
4915
  snapshot: parseSnapshot(bytes),
4788
4916
  };
@@ -4793,6 +4921,7 @@ function readyFromFrame(frame) {
4793
4921
  authorityEpoch: Number(frame.authority_epoch),
4794
4922
  headVersion: cloud.version,
4795
4923
  checksum: cloud.checksum,
4924
+ deliveryWatermark: cloud.deliveryWatermark,
4796
4925
  records: cloud.snapshot.records,
4797
4926
  },
4798
4927
  };