@amalgm/shell 0.1.125 → 0.1.127

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, readFile } 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, decodeAcceptedEntityRecord, deriveUploadManifest, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, downloadAll as downloadAllArtifacts, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, pathIsSuspect, CONTENT_REFS_BATCH_OBJECTS, privateEntityResourceId, repositoryIdentityHash, sameRecords, snapshotFromRecords, stableJson, streamCargo, travelingRecords, upload as uploadArtifact, userGroundRecords, GRAPH_HEAD_OPERATION_KIND, } 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, containedRecords, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, decodeAcceptedEntityRecord, deriveUploadManifest, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, downloadAll as downloadAllArtifacts, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, pathIsSuspect, CONTENT_REFS_BATCH_OBJECTS, privateEntityResourceId, repositoryIdentityHash, sameRecords, snapshotFromRecords, stableJson, streamCargo, travelingRecords, upload as uploadArtifact, userGroundRecords, GRAPH_HEAD_OPERATION_KIND, } 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";
@@ -11,7 +11,8 @@ import { EntityApplyHost, } from "./entity-apply-host.js";
11
11
  import { EntityRecordSqliteStore } from "./entity-record-store.js";
12
12
  import { observeEntityRecordPorts } from "./entity-record-observability.js";
13
13
  import { indexRowsByOutermostRoot } from "./ground-root-index.js";
14
- import { ContentCacheDownloadSet, captureContentFile, hashContentFile, pruneSettledContentCache, sealCapturedArtifact, } from "./content-cache-host.js";
14
+ import { ContentCacheDownloadSet, captureContentFile, hasSealedContent, hashContentFile, pruneSettledContentCache, sealCapturedArtifact, } from "./content-cache-host.js";
15
+ import { StagedContentSet, addStagingEvidenceDir, clearAddStagingEvidence, } from "./workspace-add-staging.js";
15
16
  import { DirectContentPlane } from "./content-direct.js";
16
17
  import { encodeContentWireBytes } from "./content-wire-codec.js";
17
18
  import { exactTextReplay, planGroundDetection, reconcileGroundUUIDs, } from "./detection/portable.js";
@@ -23,6 +24,7 @@ import { applyRepositoryFiles, captureRepository, inspectRepositoryTransportFile
23
24
  import { gitEvidenceFingerprint, inspectGitRegistration, sameGitIdentity, } from "./git-registration-host.js";
24
25
  import { projectMaterializedGraph } from "./materialized-graph.js";
25
26
  import { graphPageIdentities, registryGraphFromSnapshot, } from "./registry-graph.js";
27
+ import { readVerifiedRegistryPage, writeVerifiedRegistryPage, } from "./registry-page-cache.js";
26
28
  import { NodeWatchHost, } from "./watching/index.js";
27
29
  import { GroundCoordinator } from "./ground-coordination.js";
28
30
  import { adoptSnapshotDeliveryBaseline } from "./snapshot-delivery-baseline.js";
@@ -34,17 +36,17 @@ const CARGO_SEGMENT_LOAD_CONCURRENCY = 8;
34
36
  /** Storage-object lanes for one download set. Tiny objects use the wide
35
37
  * request window, while the separate byte budget narrows large chunks so
36
38
  * request concurrency can never silently become an unbounded memory claim. */
37
- const DIRECT_OBJECT_DOWNLOAD_CONCURRENCY = 128;
39
+ /** Admission is sized to the refs batch law so one same-turn wave coalesces
40
+ * into one refs frame and the span fetcher merges each pack's members into a
41
+ * few broad ranged reads. Memory is bounded by the byte laws below, never by
42
+ * this count. */
43
+ const DIRECT_OBJECT_DOWNLOAD_CONCURRENCY = CONTENT_REFS_BATCH_OBJECTS;
38
44
  // Opening one protected object transiently retains the fetched envelope,
39
45
  // WebCrypto's ciphertext-with-tag input, and the plaintext. Bound plaintext
40
46
  // to two full blocks so those three representations stay below 96 MiB even
41
47
  // on the 1 GiB runtimes used for ordinary Files materialization. Small
42
48
  // objects still use the full request-concurrency window.
43
49
  const DIRECT_OBJECT_DOWNLOAD_MAX_IN_FLIGHT_BYTES = 2 * CHUNK_BYTES;
44
- /** Graph pages are at most a few megabytes each and a whole account is a
45
- * handful of them, so a narrow lane keeps the cold read fast without holding
46
- * the whole graph twice while it assembles. */
47
- const GRAPH_PAGE_READ_CONCURRENCY = 8;
48
50
  const DETECT_QUIET_MS = 12;
49
51
  const DETECT_MAX_DEFERRAL_MS = 75;
50
52
  const DETECT_RETRY_MS = 250;
@@ -94,6 +96,9 @@ export class UserGroundHost {
94
96
  watchDirty = false;
95
97
  cloudState = null;
96
98
  converged = false;
99
+ /** Cumulative graph-page reads, reported as diffs on the stages that
100
+ * materialize roots so the ledger shows what each operation actually read. */
101
+ graphPageReads = { fetchedPages: 0, fetchedBytes: 0, cachedPages: 0 };
97
102
  syncing = null;
98
103
  recordRail = null;
99
104
  recordStore = null;
@@ -159,7 +164,7 @@ export class UserGroundHost {
159
164
  const local = this.localPort(this.activeIdentity);
160
165
  const result = await convergeUserGround(this.activeIdentity, {
161
166
  sha256Hex,
162
- cloud: this.cloudPort(this.activeIdentity),
167
+ cloud: this.cloudPort(this.activeIdentity, { kind: "core" }),
163
168
  local,
164
169
  });
165
170
  await this.resumeWorkspaceAdds(this.activeIdentity);
@@ -302,13 +307,16 @@ export class UserGroundHost {
302
307
  });
303
308
  const add = {
304
309
  sha256Hex,
305
- lookupRegistry: async () => {
310
+ lookupRegistry: async (workspaceId) => {
306
311
  const started = performance.now();
307
312
  this.stage({
308
313
  primitive: "download", stage: "registry-lookup", status: "started",
309
314
  });
315
+ const pagesBefore = { ...this.graphPageReads };
316
+ const planeBefore = this.direct.metrics();
310
317
  const resourceId = privateEntityResourceId(identity.userId, sha256Hex);
311
- const cloud = await this.cloudPort(identity).lookup(resourceId);
318
+ const cloud = await this.cloudPort(identity, { kind: "workspace", workspaceId })
319
+ .lookup(resourceId);
312
320
  if (!cloud)
313
321
  throw new Error("this user has no cloud entity registry");
314
322
  // The authority's active snapshot is the compact base; Add's active
@@ -322,7 +330,7 @@ export class UserGroundHost {
322
330
  if (deliveryWatermark < cloud.deliveryWatermark) {
323
331
  throw new Error("local delivery cursor is behind the active snapshot watermark");
324
332
  }
325
- const snapshot = snapshotFromRecords(recordsAtDeliveryPrefix(cloud.snapshot.records, readAcceptedResults(this.databasePath(identity), resourceId, cloud.deliveryWatermark, deliveryWatermark)));
333
+ const snapshot = snapshotFromRecords(await this.foldScopedPrefix(cloud.snapshot.records, readAcceptedResults(this.databasePath(identity), resourceId, cloud.deliveryWatermark, deliveryWatermark), workspaceId));
326
334
  const current = {
327
335
  ...cloud,
328
336
  deliveryWatermark,
@@ -334,6 +342,13 @@ export class UserGroundHost {
334
342
  stage: "registry-lookup",
335
343
  status: "completed",
336
344
  durationMs: performance.now() - started,
345
+ measurements: {
346
+ records: snapshot.records.length,
347
+ fetchedPages: this.graphPageReads.fetchedPages - pagesBefore.fetchedPages,
348
+ fetchedPageBytes: this.graphPageReads.fetchedBytes - pagesBefore.fetchedBytes,
349
+ cachedPages: this.graphPageReads.cachedPages - pagesBefore.cachedPages,
350
+ ...physicalPlaneDelta(planeBefore, this.direct.metrics()),
351
+ },
337
352
  });
338
353
  return current;
339
354
  },
@@ -369,7 +384,7 @@ export class UserGroundHost {
369
384
  destinationParent: input.destinationParent,
370
385
  cloudHead: input.cloudHead,
371
386
  deliveryWatermark: input.deliveryWatermark,
372
- readContent: (artifacts) => this.downloadContentSet(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: input.workspace.uuid }),
387
+ readContent: (artifacts, staged) => this.downloadContentSet(privateEntityResourceId(identity.userId, sha256Hex), this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: input.workspace.uuid }, staged),
373
388
  adoptMaterialization: (rootUUID) => {
374
389
  const settled = this.adoptCloudDeliveryBaseline(identity, input.deliveryWatermark, rootUUID);
375
390
  this.applyRail?.recordsAvailable();
@@ -478,8 +493,9 @@ export class UserGroundHost {
478
493
  if (stagedAcrossBlindInterval && hasRootRows(database, intent.resourceId, intent.workspaceId)) {
479
494
  // Rows prove that staging was verified before the crash, not that its
480
495
  // bytes remained unchanged while no watcher existed. Return this
481
- // private install to the intent-only stage and reproduce it from the
482
- // verified immutable cache before making it visible.
496
+ // private install to the intent-only stage; the retried install keeps
497
+ // the staged bytes but re-proves every retained chunk against its
498
+ // manifest before anything becomes visible.
483
499
  deleteRootRows(database, intent.resourceId, intent.workspaceId);
484
500
  }
485
501
  await installCloudWorkspace({
@@ -496,7 +512,7 @@ export class UserGroundHost {
496
512
  destinationParent: dirname(intent.destinationPath),
497
513
  cloudHead: intent.cloudHead,
498
514
  deliveryWatermark: intent.deliveryWatermark,
499
- readContent: (artifacts) => this.downloadContentSet(intent.resourceId, this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: intent.workspaceId }),
515
+ readContent: (artifacts, staged) => this.downloadContentSet(intent.resourceId, this.cacheDir(identity), artifacts.map((artifact) => ({ artifact })), 10, { journey: "add", workspaceId: intent.workspaceId }, staged),
500
516
  adoptMaterialization: (rootUUID) => {
501
517
  this.adoptCloudDeliveryBaseline(identity, intent.deliveryWatermark, rootUUID);
502
518
  this.applyRail?.recordsAvailable();
@@ -564,12 +580,29 @@ export class UserGroundHost {
564
580
  throw stageError("Watch coverage", error);
565
581
  }
566
582
  const database = this.databasePath(identity);
567
- const hasRegisteredBoundary = (records) => records.some((record) => record.uuid === workspaceId && record.status === "active")
568
- && records.some((record) => record.type === "reference"
583
+ // Both proofs are root-scoped reads: the boundary's own record from
584
+ // whichever root holds it, and its durable reference from the core root
585
+ // — the only root references lawfully live in.
586
+ const hasRegisteredBoundary = async () => {
587
+ const graph = this.cloudState?.graph;
588
+ if (!graph)
589
+ return false;
590
+ const boundaryRoot = await graph.locate(workspaceId);
591
+ if (boundaryRoot === null)
592
+ return false;
593
+ const boundary = (await graph.recordsFor(boundaryRoot))
594
+ .find((record) => record.uuid === workspaceId);
595
+ if (!boundary || boundary.status !== "active")
596
+ return false;
597
+ const coreRoot = await this.coreRootId(identity, graph);
598
+ if (coreRoot === null)
599
+ return false;
600
+ return (await graph.recordsFor(coreRoot)).some((record) => record.type === "reference"
569
601
  && record.status === "active"
570
602
  && record.payloadVersion === workspaceId);
603
+ };
571
604
  if (hasRegisteredBoundaryInDatabase(database, workspaceId)
572
- && hasRegisteredBoundary(this.cloudState.graph.records())) {
605
+ && await hasRegisteredBoundary()) {
573
606
  // Already declared — but Register may only return once cargo closure
574
607
  // holds under the current content contract. The graph head can be
575
608
  // unchanged while a contract cutover moved its cargo namespace, so
@@ -751,7 +784,7 @@ export class UserGroundHost {
751
784
  if (!hasRegisteredBoundaryInDatabase(database, workspaceId)) {
752
785
  throw stageError("entity registration", new Error(`workspace ${workspaceId} and its durable reference were not recorded`));
753
786
  }
754
- if (!hasRegisteredBoundary(this.cloudState.graph.records())) {
787
+ if (!(await hasRegisteredBoundary())) {
755
788
  throw stageError("cloud graph commit", new Error(`workspace ${workspaceId} or its durable reference is absent from the committed head`));
756
789
  }
757
790
  return {
@@ -1128,7 +1161,7 @@ export class UserGroundHost {
1128
1161
  cacheDir(identity) {
1129
1162
  return join(liveMachineStateDir(this.userRoot(identity), identity.deviceId), "content");
1130
1163
  }
1131
- cloudPort(identity) {
1164
+ cloudPort(identity, scope) {
1132
1165
  return {
1133
1166
  lookup: async (resourceId) => {
1134
1167
  await this.publishPendingSnapshotsBeforeLookup(identity, resourceId);
@@ -1139,7 +1172,8 @@ export class UserGroundHost {
1139
1172
  }, ["private.resource.ready", "private.resource.missing"]);
1140
1173
  if (frame.type === "private.resource.missing")
1141
1174
  return null;
1142
- const ready = readyFromFrame(frame, await this.materializeRegistry(identity, frame));
1175
+ const graph = this.materializeRegistry(identity, frame);
1176
+ const ready = readyFromFrame(frame, graph, await this.selectScope(identity, graph, scope));
1143
1177
  this.cloudState = ready.state;
1144
1178
  return ready.cloud;
1145
1179
  },
@@ -1154,7 +1188,8 @@ export class UserGroundHost {
1154
1188
  snapshot_checksum: input.checksum,
1155
1189
  snapshot_bytes: bytes.length,
1156
1190
  }, ["private.resource.ready"]);
1157
- const ready = readyFromFrame(frame, await this.materializeRegistry(identity, frame));
1191
+ const graph = this.materializeRegistry(identity, frame);
1192
+ const ready = readyFromFrame(frame, graph, await this.selectScope(identity, graph, scope));
1158
1193
  const current = ready.cloud;
1159
1194
  this.cloudState = ready.state;
1160
1195
  const created = frame.duplicate === false;
@@ -1164,60 +1199,103 @@ export class UserGroundHost {
1164
1199
  },
1165
1200
  };
1166
1201
  }
1167
- /** The authority's stored snapshot as this machine's graph. A head names
1168
- * immutable pages, so every page this machine already cached is a local read
1169
- * and an unchanged root never crosses the network twice. */
1170
- async materializeRegistry(identity, frame) {
1202
+ /** The authority's stored snapshot as this machine's graph. A head is held
1203
+ * lazily — no page is read until an operation asks for a root — and every
1204
+ * page this machine already cached is a local read, so an unchanged root
1205
+ * never crosses the network twice. */
1206
+ materializeRegistry(identity, frame) {
1171
1207
  const resourceId = String(frame.resource_id || "");
1172
- return registryGraphFromSnapshot(Buffer.from(String(frame.snapshot_b64 || ""), "base64"), (refs) => this.readGraphPages(identity, resourceId, refs), sha256Hex);
1173
- }
1174
- /** Read graph pages by identity: cached pages come off local disk, the rest
1175
- * are fetched straight from content storage and cached on the way in. Every
1176
- * page is re-proven against its declared identity before it is kept. */
1177
- async readGraphPages(identity, resourceId, refs) {
1178
- const directory = graphPageCacheDir(this.cacheDir(identity));
1179
- const pages = new Map();
1180
- const missing = [];
1181
- for (const ref of new Map(refs.map((ref) => [ref.sha256, ref])).values()) {
1182
- let cached = null;
1183
- try {
1184
- cached = readFileSync(join(directory, `${ref.sha256}.bin`));
1185
- }
1186
- catch {
1187
- cached = null;
1188
- }
1189
- // A cache copy of the wrong size is torn evidence, never a page: fetch
1190
- // the immutable original instead of assembling from it.
1191
- if (cached && cached.byteLength === ref.bytes)
1192
- pages.set(ref.sha256, cached);
1193
- else
1194
- missing.push(ref);
1195
- }
1196
- if (missing.length === 0)
1197
- return pages;
1198
- const started = performance.now();
1199
- let fetchedBytes = 0;
1200
- await boundedForEach(missing, GRAPH_PAGE_READ_CONCURRENCY, async (ref) => {
1201
- const bytes = await retryContent(() => this.direct.fetchObject(resourceId, { kind: "block", sha256: ref.sha256 }), true);
1202
- if (bytes.byteLength !== ref.bytes || sha256Hex(bytes) !== ref.sha256) {
1203
- throw new Error(`cloud graph page ${ref.sha256} does not match its bytes`);
1208
+ return registryGraphFromSnapshot(Buffer.from(String(frame.snapshot_b64 || ""), "base64"), (ref) => this.readGraphPage(identity, resourceId, ref), sha256Hex);
1209
+ }
1210
+ /** The records a lookup hands to Live: every root's own record — the
1211
+ * discovery set that proves root uniqueness — plus the full records of
1212
+ * exactly the roots the operation acts on. A records-shaped snapshot is a
1213
+ * first publication's own small graph and travels whole, keeping its
1214
+ * records-shape checksum law intact. */
1215
+ async selectScope(identity, graph, scope) {
1216
+ if (graph.head === null)
1217
+ return graph.recordsShapedSnapshot().records;
1218
+ if (scope.kind === "none")
1219
+ return [];
1220
+ const selected = new Set();
1221
+ const core = await this.coreRootId(identity, graph);
1222
+ if (core)
1223
+ selected.add(core);
1224
+ if (scope.kind === "workspace") {
1225
+ const boundaryRoot = await graph.locate(scope.workspaceId);
1226
+ if (boundaryRoot)
1227
+ selected.add(boundaryRoot);
1228
+ }
1229
+ const records = new Map((await graph.rootRecords())
1230
+ .map((record) => [record.uuid, record]));
1231
+ for (const rootId of selected) {
1232
+ for (const record of await graph.recordsFor(rootId))
1233
+ records.set(record.uuid, record);
1234
+ }
1235
+ return [...records.values()];
1236
+ }
1237
+ /** The account's core root: the one active top-level workspace named by the
1238
+ * account email. Registered-boundary references live only there. */
1239
+ async coreRootId(identity, graph) {
1240
+ const email = identity.userEmail.toLowerCase();
1241
+ const core = (await graph.rootRecords()).find((record) => record.status === "active"
1242
+ && record.parentUUID === null
1243
+ && record.type === "workspace"
1244
+ && record.name.toLowerCase() === email);
1245
+ return core?.uuid ?? null;
1246
+ }
1247
+ /** Fold the committed delivery tail over a root-scoped base without losing
1248
+ * a subtree the tail moved across roots. A container the folded boundary
1249
+ * contains only through delivered results may still file unchanged
1250
+ * descendants under the root that used to hold it, so that root's records
1251
+ * join the base and the fold reruns until no probe widens it. Leaf results
1252
+ * travel whole and never probe. */
1253
+ async foldScopedPrefix(base, accepted, boundaryId) {
1254
+ if (accepted.length === 0)
1255
+ return base;
1256
+ const graph = this.cloudState?.graph;
1257
+ if (!graph)
1258
+ throw new Error("cloud state is unavailable for a registry lookup");
1259
+ const probed = new Set(base.map((record) => record.uuid));
1260
+ let widened = base;
1261
+ for (;;) {
1262
+ const folded = recordsAtDeliveryPrefix(widened, accepted);
1263
+ const probes = containedRecords(folded, boundaryId)
1264
+ .filter((record) => !probed.has(record.uuid)
1265
+ && (record.type === "workspace" || record.type === "folder" || record.type === "repo.git"))
1266
+ .map((record) => record.uuid);
1267
+ if (probes.length === 0)
1268
+ return folded;
1269
+ for (const uuid of probes)
1270
+ probed.add(uuid);
1271
+ const located = await graph.membership(new Set(probes));
1272
+ const merged = new Map(widened.map((record) => [record.uuid, record]));
1273
+ for (const rootId of new Set(located.values())) {
1274
+ for (const record of await graph.recordsFor(rootId)) {
1275
+ merged.set(record.uuid, record);
1276
+ }
1204
1277
  }
1205
- atomicWrite(join(directory, `${ref.sha256}.bin`), bytes);
1206
- fetchedBytes += bytes.byteLength;
1207
- pages.set(ref.sha256, bytes);
1208
- });
1209
- this.stage({
1210
- primitive: "download",
1211
- stage: "graph-pages",
1212
- status: "completed",
1213
- durationMs: performance.now() - started,
1214
- measurements: {
1215
- pages: refs.length,
1216
- fetchedPages: missing.length,
1217
- fetchedBytes,
1218
- },
1219
- });
1220
- return pages;
1278
+ if (merged.size === widened.length)
1279
+ return folded;
1280
+ widened = [...merged.values()];
1281
+ }
1282
+ }
1283
+ /** Read one graph page by identity: cached pages come off local disk, the
1284
+ * rest are fetched straight from content storage and cached on the way in.
1285
+ * The graph proves every page against its declared identity as it parses,
1286
+ * so this reader only guards the cache against torn copies. */
1287
+ async readGraphPage(identity, resourceId, ref) {
1288
+ const path = join(graphPageCacheDir(this.cacheDir(identity)), `${ref.sha256}.bin`);
1289
+ const cached = readVerifiedRegistryPage(path, ref, sha256Hex);
1290
+ if (cached) {
1291
+ this.graphPageReads.cachedPages += 1;
1292
+ return cached;
1293
+ }
1294
+ const bytes = await retryContent(() => this.direct.fetchObject(resourceId, { kind: "block", sha256: ref.sha256 }), true);
1295
+ writeVerifiedRegistryPage(path, ref, bytes, sha256Hex);
1296
+ this.graphPageReads.fetchedPages += 1;
1297
+ this.graphPageReads.fetchedBytes += bytes.byteLength;
1298
+ return bytes;
1221
1299
  }
1222
1300
  localPort(identity) {
1223
1301
  const userRoot = this.userRoot(identity);
@@ -1600,15 +1678,18 @@ export class UserGroundHost {
1600
1678
  * manifests and blocks alike — shares one wide lane bound, so no artifact
1601
1679
  * serializes behind another's plan or seal. Each object is still its own
1602
1680
  * storage request. A lone artifact is a set of one. */
1603
- async downloadContentSet(resourceId, cacheDir, wants, retryAttempts = 10, trace) {
1681
+ async downloadContentSet(resourceId, cacheDir, wants, retryAttempts = 10, trace, staged) {
1604
1682
  const unique = new Map(wants.map((want) => [want.artifact.contentHash, want]));
1605
1683
  const sole = unique.size === 1 ? [...unique.values()][0]?.artifact : undefined;
1606
1684
  const started = performance.now();
1685
+ const planeBefore = this.direct.metrics();
1607
1686
  let declaredBytes = 0;
1608
1687
  let downloadedBytes = 0;
1609
1688
  let downloadedChunks = 0;
1610
1689
  let reusedChunks = 0;
1611
1690
  let requests = 0;
1691
+ let cacheWriteMs = 0;
1692
+ let stagedArtifacts = 0;
1612
1693
  const entityId = trace?.entityId ?? sole?.entityId;
1613
1694
  const identifiers = {
1614
1695
  ...(trace?.workspaceId ? { workspaceId: trace.workspaceId } : {}),
@@ -1628,6 +1709,7 @@ export class UserGroundHost {
1628
1709
  },
1629
1710
  });
1630
1711
  const downloads = new ContentCacheDownloadSet(cacheDir);
1712
+ const stagedSet = staged ? new StagedContentSet(staged.evidenceDir) : null;
1631
1713
  const targets = new Map();
1632
1714
  const target = (artifact) => {
1633
1715
  const found = targets.get(artifact.contentHash);
@@ -1662,7 +1744,18 @@ export class UserGroundHost {
1662
1744
  throw new Error(checked.error);
1663
1745
  return checked.value;
1664
1746
  })();
1665
- targets.set(artifact.contentHash, downloads.target(artifact, manifest));
1747
+ // An Add stages ordinary files directly at their hidden final
1748
+ // destination — except content this machine already holds sealed,
1749
+ // which stays on the cache rail so a warm re-Add remains a local
1750
+ // clone instead of a fresh download.
1751
+ const stagedPaths = stagedSet && staged?.destinations.get(artifact.contentHash);
1752
+ if (stagedPaths && !hasSealedContent(cacheDir, artifact.contentHash, manifest.bytes)) {
1753
+ stagedArtifacts += 1;
1754
+ targets.set(artifact.contentHash, stagedSet.target(artifact, manifest, stagedPaths));
1755
+ }
1756
+ else {
1757
+ targets.set(artifact.contentHash, downloads.target(artifact, manifest));
1758
+ }
1666
1759
  return manifest;
1667
1760
  },
1668
1761
  hasChunk: async (artifact, _manifest, index) => target(artifact).hasChunk(index),
@@ -1673,10 +1766,14 @@ export class UserGroundHost {
1673
1766
  return fetchObject({ kind: "block", sha256: chunk.sha256 });
1674
1767
  },
1675
1768
  putChunk: async (artifact, _manifest, index, bytes) => {
1769
+ const writeStarted = performance.now();
1676
1770
  await target(artifact).putChunk(index, bytes);
1771
+ cacheWriteMs += performance.now() - writeStarted;
1677
1772
  },
1678
1773
  sealArtifact: async (artifact) => {
1774
+ const sealStarted = performance.now();
1679
1775
  const sealed = await target(artifact).seal();
1776
+ cacheWriteMs += performance.now() - sealStarted;
1680
1777
  return { local: sealed, bytes: sealed.bytes, contentHash: sealed.contentHash };
1681
1778
  },
1682
1779
  }, {
@@ -1704,7 +1801,15 @@ export class UserGroundHost {
1704
1801
  downloadedBytes,
1705
1802
  downloadedChunks,
1706
1803
  reusedChunks,
1804
+ // `requests` is logical object reads; the plane deltas below are
1805
+ // the physical facts — actual storage GETs, sealed bytes on the
1806
+ // wire, refs round-trips, decrypt and local-write CPU.
1807
+ // `cacheWriteMs` is all local placement time, cache and staged
1808
+ // rails alike; `stagedArtifacts` counts single-write placements.
1707
1809
  requests,
1810
+ ...physicalPlaneDelta(planeBefore, this.direct.metrics()),
1811
+ cacheWriteMs: Math.round(cacheWriteMs),
1812
+ ...(stagedArtifacts > 0 ? { stagedArtifacts } : {}),
1708
1813
  },
1709
1814
  });
1710
1815
  return content;
@@ -1728,6 +1833,7 @@ export class UserGroundHost {
1728
1833
  finally {
1729
1834
  await Promise.all([...targets.values()].map((open) => open.close()));
1730
1835
  downloads.close();
1836
+ stagedSet?.close();
1731
1837
  }
1732
1838
  }
1733
1839
  async downloadContent(resourceId, cacheDir, artifact, retryAttempts = 10, trace, knownManifest) {
@@ -2124,7 +2230,8 @@ export class UserGroundHost {
2124
2230
  // the roots it rebuilt — so the machine re-reads the authority's head
2125
2231
  // before deriving the next boundary on top of it.
2126
2232
  if (!this.cloudState) {
2127
- await this.cloudPort(identity).lookup(privateEntityResourceId(identity.userId, sha256Hex));
2233
+ await this.cloudPort(identity, { kind: "none" })
2234
+ .lookup(privateEntityResourceId(identity.userId, sha256Hex));
2128
2235
  }
2129
2236
  const state = this.cloudState;
2130
2237
  if (!state)
@@ -2155,6 +2262,12 @@ export class UserGroundHost {
2155
2262
  leg[`${name}Ms`] = performance.now() - started;
2156
2263
  return value;
2157
2264
  };
2265
+ const timedAsync = async (name, work) => {
2266
+ const started = performance.now();
2267
+ const value = await work();
2268
+ leg[`${name}Ms`] = performance.now() - started;
2269
+ return value;
2270
+ };
2158
2271
  if (trace?.journey === "register") {
2159
2272
  this.stage({
2160
2273
  primitive: "register",
@@ -2167,7 +2280,7 @@ export class UserGroundHost {
2167
2280
  .map(portableRecord), readGroundRowsForRoots(this.databasePath(identity), "detection_notebook", publicationRootIds)
2168
2281
  .map(portableRecord)));
2169
2282
  const accepted = timed("officialRows", () => readAcceptedResults(this.databasePath(identity), state.resourceId, state.deliveryWatermark, deliveryWatermark));
2170
- const rebuilt = timed("merge", () => publicationRoots(state.graph, localRecords, accepted));
2283
+ const rebuilt = await timedAsync("merge", () => publicationRoots(state.graph, localRecords, accepted));
2171
2284
  const projected = timed("project", () => new Map([...rebuilt].map(([rootId, records]) => [rootId, records.length === 0 ? records : travelingRecords(records, sha256Hex)])));
2172
2285
  const traveling = [...projected.values()].flat();
2173
2286
  const impossibleGitLeaf = traveling.find((record) => (record.type === "file.text" || record.type === "file.binary" || record.type === "link")
@@ -2179,7 +2292,7 @@ export class UserGroundHost {
2179
2292
  // ONE canonical serialization per rebuilt root: the page bytes are the
2180
2293
  // journal's payload, the upload's payload, and the head's identities.
2181
2294
  // Untouched roots are not serialized, hashed, or journaled at all.
2182
- const publication = timed("pages", () => state.graph.rebuild(projected, sha256Hex));
2295
+ const publication = timed("pages", () => state.graph.rebuild(projected));
2183
2296
  const checksum = publication.checksum;
2184
2297
  const localIds = new Set(localRecords.map((record) => record.uuid));
2185
2298
  // Graph membership is never proof of sealed cargo: a record in the cloud
@@ -5177,7 +5290,7 @@ function recordRootResolver(records) {
5177
5290
  * touched is rebuilt from the committed graph with those results folded in, so
5178
5291
  * the published graph never declares a watermark it has not actually applied.
5179
5292
  * Every root outside this map keeps its committed pages untouched. */
5180
- function publicationRoots(graph, localRecords, accepted) {
5293
+ async function publicationRoots(graph, localRecords, accepted) {
5181
5294
  const rebuilt = new Map();
5182
5295
  const walked = new Map(localRecords
5183
5296
  .filter((record) => record.parentUUID === null)
@@ -5194,9 +5307,16 @@ function publicationRoots(graph, localRecords, accepted) {
5194
5307
  for (const [rootId, records] of walked)
5195
5308
  rebuilt.set(rootId, records);
5196
5309
  }
5310
+ // One page-bounded sweep answers every committed-root question this merge
5311
+ // asks: which root holds each accepted result, and which root still files
5312
+ // each record the walk re-rooted.
5313
+ const located = await graph.membership(new Set([
5314
+ ...accepted.map((result) => result.uuid),
5315
+ ...localRecords.map((record) => record.uuid),
5316
+ ]));
5197
5317
  const overlays = new Map();
5198
5318
  for (const result of accepted) {
5199
- const rootId = graph.rootOf(result.uuid);
5319
+ const rootId = located.get(result.uuid) ?? null;
5200
5320
  if (rootId === null || walked.has(rootId))
5201
5321
  continue;
5202
5322
  const overlay = overlays.get(rootId);
@@ -5206,7 +5326,8 @@ function publicationRoots(graph, localRecords, accepted) {
5206
5326
  overlays.set(rootId, [result]);
5207
5327
  }
5208
5328
  for (const [rootId, results] of overlays) {
5209
- const byUuid = new Map(graph.recordsFor(rootId).map((record) => [record.uuid, record]));
5329
+ const byUuid = new Map((await graph.recordsFor(rootId))
5330
+ .map((record) => [record.uuid, record]));
5210
5331
  for (const result of results)
5211
5332
  byUuid.set(result.uuid, result);
5212
5333
  // Entity channels are independently ordered, so container membership is
@@ -5219,7 +5340,7 @@ function publicationRoots(graph, localRecords, accepted) {
5219
5340
  if (graph.head === null) {
5220
5341
  for (const rootId of graph.rootIds()) {
5221
5342
  if (!rebuilt.has(rootId))
5222
- rebuilt.set(rootId, graph.recordsFor(rootId));
5343
+ rebuilt.set(rootId, await graph.recordsFor(rootId));
5223
5344
  }
5224
5345
  }
5225
5346
  // Re-rooting is a move, not a copy: an enclosing Register walks records the
@@ -5228,7 +5349,7 @@ function publicationRoots(graph, localRecords, accepted) {
5228
5349
  // with no records at all — the rebuild then drops it from the graph.
5229
5350
  const vacated = new Map();
5230
5351
  for (const record of localRecords) {
5231
- const rootId = graph.rootOf(record.uuid);
5352
+ const rootId = located.get(record.uuid) ?? null;
5232
5353
  if (rootId === null || walked.has(rootId))
5233
5354
  continue;
5234
5355
  const gone = vacated.get(rootId);
@@ -5238,7 +5359,7 @@ function publicationRoots(graph, localRecords, accepted) {
5238
5359
  vacated.set(rootId, new Set([record.uuid]));
5239
5360
  }
5240
5361
  for (const [rootId, gone] of vacated) {
5241
- const held = rebuilt.get(rootId) ?? graph.recordsFor(rootId);
5362
+ const held = rebuilt.get(rootId) ?? await graph.recordsFor(rootId);
5242
5363
  rebuilt.set(rootId, held.filter((record) => !gone.has(record.uuid)));
5243
5364
  }
5244
5365
  return rebuilt;
@@ -5336,9 +5457,17 @@ function installCloudGround(input) {
5336
5457
  })();
5337
5458
  }
5338
5459
  async function materializeRecords(input) {
5339
- const { records, rootPath, cacheDir, bindingDir, readContent } = input;
5460
+ const { records, rootPath, cacheDir, bindingDir, staged, readContent } = input;
5340
5461
  const repositories = [];
5341
5462
  const repositoriesByUuid = new Map();
5463
+ const paths = portablePaths(records, input.boundaryUUID ? new Set([input.boundaryUUID]) : new Set());
5464
+ const destinationOf = (relativePath) => {
5465
+ const destination = relativePath ? join(rootPath, ...relativePath.split("/")) : rootPath;
5466
+ if (!pathWithin(rootPath, destination)) {
5467
+ throw new Error(`cloud entity path escapes its workspace: ${relativePath}`);
5468
+ }
5469
+ return destination;
5470
+ };
5342
5471
  // One download set covers everything the wire owes this add up front:
5343
5472
  // every ordinary file artifact and every repository transport head, side
5344
5473
  // by side under one wide object bound. Only transport parents — links a
@@ -5346,6 +5475,7 @@ async function materializeRecords(input) {
5346
5475
  // round is again one set across every chain. Disk application below keeps
5347
5476
  // its ordered, validated walk.
5348
5477
  const wanted = new Map();
5478
+ const stagedDestinations = staged ? new Map() : null;
5349
5479
  const repositoryHeads = [];
5350
5480
  for (const record of records) {
5351
5481
  if (record.type === "repo.git") {
@@ -5360,11 +5490,30 @@ async function materializeRecords(input) {
5360
5490
  }
5361
5491
  else if (["file.text", "file.binary", "link"].includes(record.type)) {
5362
5492
  const artifact = artifactForRecord(record);
5363
- if (artifact)
5364
- wanted.set(artifact.contentHash, artifact);
5493
+ if (!artifact)
5494
+ continue;
5495
+ wanted.set(artifact.contentHash, artifact);
5496
+ // Links and repository transports keep the cache rail: a link's
5497
+ // small payload is verified there and becomes a symlink, and a
5498
+ // transport is never its final worktree. Ordinary file bytes are
5499
+ // the cold Add's mass, and they land exactly once.
5500
+ if (stagedDestinations && record.type !== "link") {
5501
+ const destination = destinationOf(paths.get(record.uuid));
5502
+ const list = stagedDestinations.get(artifact.contentHash) ?? [];
5503
+ list.push(destination);
5504
+ stagedDestinations.set(artifact.contentHash, list);
5505
+ }
5365
5506
  }
5366
5507
  }
5367
- const fileContent = await readContent([...wanted.values()]);
5508
+ if (stagedDestinations) {
5509
+ // A deterministic primary destination keeps resume evidence valid: the
5510
+ // same first path receives the stream on every attempt.
5511
+ for (const list of stagedDestinations.values())
5512
+ list.sort();
5513
+ }
5514
+ const fileContent = await readContent([...wanted.values()], staged && stagedDestinations
5515
+ ? { evidenceDir: staged.evidenceDir, destinations: stagedDestinations }
5516
+ : undefined);
5368
5517
  const repositoryChains = new Map();
5369
5518
  const walking = repositoryHeads.map(({ record, artifact }) => {
5370
5519
  const local = fileContent.get(artifact.contentHash);
@@ -5418,7 +5567,6 @@ async function materializeRecords(input) {
5418
5567
  repositories.push(repository);
5419
5568
  repositoriesByUuid.set(record.uuid, repository);
5420
5569
  };
5421
- const paths = portablePaths(records, input.boundaryUUID ? new Set([input.boundaryUUID]) : new Set());
5422
5570
  const recordsByUuid = new Map(records.map((record) => [record.uuid, record]));
5423
5571
  const repositoryOwner = (record) => {
5424
5572
  let parentUUID = record.parentUUID;
@@ -5456,29 +5604,59 @@ async function materializeRecords(input) {
5456
5604
  return leftPath.split("/").length - rightPath.split("/").length
5457
5605
  || (leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0);
5458
5606
  });
5607
+ // Staged mode walks ground that may be retained from an interrupted Add:
5608
+ // files were already re-proven by the download, directories are lawful
5609
+ // skeleton, and symlinks and repositories are recreated. Existence alone
5610
+ // therefore no longer proves a record collision — the walk's own claims
5611
+ // do, case- and normalization-folded because filesystems merge such
5612
+ // names into one physical path.
5613
+ const claimed = staged ? new Set() : null;
5614
+ const claim = (relativePath) => {
5615
+ if (!claimed || relativePath === "")
5616
+ return;
5617
+ const key = relativePath.normalize("NFC").toLowerCase();
5618
+ if (claimed.has(key)) {
5619
+ throw new Error(`cloud materialization conflicts with ${relativePath}`);
5620
+ }
5621
+ claimed.add(key);
5622
+ };
5459
5623
  for (const record of ordered) {
5460
5624
  const relativePath = paths.get(record.uuid);
5461
- const destination = relativePath ? join(rootPath, ...relativePath.split("/")) : rootPath;
5462
- if (!pathWithin(rootPath, destination)) {
5463
- throw new Error(`cloud entity path escapes its workspace: ${relativePath}`);
5464
- }
5625
+ const destination = destinationOf(relativePath);
5626
+ claim(relativePath);
5465
5627
  if (relativePath === "") {
5466
- if (record.type === "repo.git")
5628
+ if (record.type === "repo.git") {
5629
+ if (staged && pathExists(destination)) {
5630
+ rmSync(destination, { recursive: true, force: true });
5631
+ }
5467
5632
  await materializeRepository(record, destination);
5468
- else
5633
+ }
5634
+ else {
5469
5635
  ensureUserDir(destination);
5636
+ }
5470
5637
  }
5471
5638
  else if (record.type === "folder") {
5472
- if (pathExists(destination)) {
5639
+ if (!pathExists(destination)) {
5640
+ ensureUserDir(destination);
5641
+ }
5642
+ else if (!staged || repositoryOwner(record)) {
5473
5643
  assertRepositoryBoundaryPath(record, relativePath, destination);
5474
5644
  }
5475
5645
  else {
5476
- ensureUserDir(destination);
5646
+ const stats = lstatSync(destination);
5647
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
5648
+ throw new Error(`cloud materialization conflicts with ${relativePath}`);
5649
+ }
5477
5650
  }
5478
5651
  }
5479
5652
  else if (record.type === "repo.git") {
5480
5653
  if (pathExists(destination)) {
5481
- assertRepositoryBoundaryPath(record, relativePath, destination);
5654
+ if (!staged || repositoryOwner(record)) {
5655
+ assertRepositoryBoundaryPath(record, relativePath, destination);
5656
+ }
5657
+ else {
5658
+ rmSync(destination, { recursive: true, force: true });
5659
+ }
5482
5660
  }
5483
5661
  await materializeRepository(record, destination);
5484
5662
  }
@@ -5486,8 +5664,11 @@ async function materializeRecords(input) {
5486
5664
  if (!record.payloadVersion || !UUID.test(record.payloadVersion)) {
5487
5665
  throw new Error(`cloud reference ${record.uuid} has no workspace identity`);
5488
5666
  }
5489
- if (pathExists(destination))
5667
+ if (staged)
5668
+ rmSync(destination, { force: true });
5669
+ else if (pathExists(destination)) {
5490
5670
  throw new Error(`cloud materialization conflicts with ${relativePath}`);
5671
+ }
5491
5672
  ensureUserDir(dirname(destination));
5492
5673
  const binding = join(bindingDir, record.payloadVersion.toLowerCase());
5493
5674
  symlinkSync(relative(dirname(destination), binding), destination);
@@ -5495,21 +5676,41 @@ async function materializeRecords(input) {
5495
5676
  else if (["file.text", "file.binary", "link"].includes(record.type)) {
5496
5677
  if (!record.payloadVersion)
5497
5678
  throw new Error(`cloud leaf ${record.uuid} has no content head`);
5498
- if (pathExists(destination))
5499
- throw new Error(`cloud materialization conflicts with ${relativePath}`);
5500
5679
  const artifact = artifactForRecord(record);
5501
5680
  if (!artifact)
5502
5681
  throw new Error(`cloud leaf ${record.uuid} has no transfer artifact`);
5503
5682
  const local = fileContent.get(artifact.contentHash);
5504
5683
  if (!local)
5505
5684
  throw new Error(`cloud leaf ${record.uuid} content was not downloaded`);
5506
- ensureUserDir(dirname(destination));
5507
- if (record.type === "link")
5685
+ if (record.type === "link") {
5686
+ if (staged)
5687
+ rmSync(destination, { force: true });
5688
+ else if (pathExists(destination)) {
5689
+ throw new Error(`cloud materialization conflicts with ${relativePath}`);
5690
+ }
5691
+ ensureUserDir(dirname(destination));
5508
5692
  symlinkSync(readFileSync(local.file, "utf8"), destination);
5509
- // Mode is not portable state: a fresh user file gets 0644, the same
5510
- // default Apply gives a fresh target, never the cache's private 0600.
5511
- else
5693
+ }
5694
+ else if (staged && pathWithin(rootPath, local.file)) {
5695
+ // The download already wrote, proved, and fsynced these bytes at
5696
+ // their destination — the single-write law. The walk only proves
5697
+ // the placement still stands.
5698
+ const stats = lstatSync(destination);
5699
+ if (!stats.isFile() || stats.isSymbolicLink() || stats.size !== local.bytes) {
5700
+ throw new Error(`cloud content ${local.contentHash} is corrupt`);
5701
+ }
5702
+ }
5703
+ else {
5704
+ if (staged)
5705
+ rmSync(destination, { force: true });
5706
+ else if (pathExists(destination)) {
5707
+ throw new Error(`cloud materialization conflicts with ${relativePath}`);
5708
+ }
5709
+ ensureUserDir(dirname(destination));
5710
+ // Mode is not portable state: a fresh user file gets 0644, the same
5711
+ // default Apply gives a fresh target, never the cache's private 0600.
5512
5712
  atomicCopy(local.file, destination, 0o644);
5713
+ }
5513
5714
  }
5514
5715
  else {
5515
5716
  throw new Error(`materialization for ${record.type} requires its dedicated adapter`);
@@ -5654,6 +5855,7 @@ async function installCloudWorkspace(input) {
5654
5855
  throw new Error(`local workspace ${workspace.uuid} has unresolved placement`);
5655
5856
  }
5656
5857
  renameSync(pending.stagingPath, pending.destinationPath);
5858
+ clearAddStagingEvidence(database, workspace.uuid);
5657
5859
  revealed = true;
5658
5860
  }
5659
5861
  else if (pending && pathExists(pending.stagingPath)) {
@@ -5661,6 +5863,7 @@ async function installCloudWorkspace(input) {
5661
5863
  }
5662
5864
  if (pending && pathExists(root.absolutePath)) {
5663
5865
  refreshRevealedRootEvidence(database, workspace.uuid, root.absolutePath);
5866
+ clearAddStagingEvidence(database, workspace.uuid);
5664
5867
  }
5665
5868
  if (revealed)
5666
5869
  await onStage?.("ground-revealed");
@@ -5762,9 +5965,9 @@ async function installCloudWorkspace(input) {
5762
5965
  if (pathExists(binding)) {
5763
5966
  throw new Error(`workspace ${workspace.uuid} has a binding without committed entity rows`);
5764
5967
  }
5765
- if (pathExists(intent.stagingPath)) {
5766
- rmSync(intent.stagingPath, { recursive: true, force: true });
5767
- }
5968
+ // Staging retained from an interrupted Add is deliberately kept: the
5969
+ // staged download re-proves every retained chunk against its manifest and
5970
+ // downloads only the gaps. Unverified staged bytes are never trusted.
5768
5971
  const selection = workspaceAddSelection(intent);
5769
5972
  const materialized = await materializeRecords({
5770
5973
  records: intent.records,
@@ -5772,6 +5975,7 @@ async function installCloudWorkspace(input) {
5772
5975
  boundaryUUID: intent.workspaceId,
5773
5976
  cacheDir,
5774
5977
  bindingDir,
5978
+ staged: { evidenceDir: addStagingEvidenceDir(database, intent.workspaceId) },
5775
5979
  readContent,
5776
5980
  });
5777
5981
  const rows = projectMaterializedGraph(materialized.rows, materialized.repositories.map((repository) => ({
@@ -5788,6 +5992,7 @@ async function installCloudWorkspace(input) {
5788
5992
  input.adoptMaterialization(intent.workspaceId);
5789
5993
  await onStage?.("rows-committed");
5790
5994
  renameSync(intent.stagingPath, intent.destinationPath);
5995
+ clearAddStagingEvidence(database, intent.workspaceId);
5791
5996
  refreshRevealedRootEvidence(database, intent.workspaceId, intent.destinationPath);
5792
5997
  await onStage?.("ground-revealed");
5793
5998
  ensureWorkspaceBinding({
@@ -5814,13 +6019,15 @@ async function installCloudWorkspace(input) {
5814
6019
  alreadyMaterialized: false,
5815
6020
  };
5816
6021
  }
5817
- function readyFromFrame(frame, graph) {
6022
+ /** The authority's ready frame as Live sees it. The snapshot carries only the
6023
+ * records the caller's scope selected — never a flattened whole account. */
6024
+ function readyFromFrame(frame, graph, records) {
5818
6025
  const cloud = {
5819
6026
  resourceId: String(frame.resource_id || ""),
5820
6027
  version: Number(frame.head_version),
5821
6028
  deliveryWatermark: Number(frame.delivery_watermark),
5822
6029
  checksum: String(frame.snapshot_checksum || ""),
5823
- snapshot: graph.snapshot(),
6030
+ snapshot: snapshotFromRecords(records),
5824
6031
  ...(graph.head ? { head: graph.head } : {}),
5825
6032
  };
5826
6033
  return {
@@ -5835,6 +6042,18 @@ function readyFromFrame(frame, graph) {
5835
6042
  },
5836
6043
  };
5837
6044
  }
6045
+ /** What one leg physically did on the content plane: the counter movement
6046
+ * between its start and end, ms rounded for the ledger. */
6047
+ function physicalPlaneDelta(before, after) {
6048
+ return {
6049
+ rangedGets: after.rangedGets - before.rangedGets,
6050
+ wholeGets: after.wholeGets - before.wholeGets,
6051
+ fetchedSealedBytes: after.fetchedBytes - before.fetchedBytes,
6052
+ refsFrames: after.refsFrames - before.refsFrames,
6053
+ refsMs: Math.round(after.refsMs - before.refsMs),
6054
+ decryptMs: Math.round(after.decryptMs - before.decryptMs),
6055
+ };
6056
+ }
5838
6057
  /** Immutable content reads and writes are idempotent. Retry transient wire or
5839
6058
  * authority failures; reads additionally tolerate the brief not-found window
5840
6059
  * between an immutable object acknowledgement and globally visible R2 state. */