@amalgm/shell 0.1.124 → 0.1.126

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";
@@ -23,6 +23,7 @@ import { applyRepositoryFiles, captureRepository, inspectRepositoryTransportFile
23
23
  import { gitEvidenceFingerprint, inspectGitRegistration, sameGitIdentity, } from "./git-registration-host.js";
24
24
  import { projectMaterializedGraph } from "./materialized-graph.js";
25
25
  import { graphPageIdentities, registryGraphFromSnapshot, } from "./registry-graph.js";
26
+ import { readVerifiedRegistryPage, writeVerifiedRegistryPage, } from "./registry-page-cache.js";
26
27
  import { NodeWatchHost, } from "./watching/index.js";
27
28
  import { GroundCoordinator } from "./ground-coordination.js";
28
29
  import { adoptSnapshotDeliveryBaseline } from "./snapshot-delivery-baseline.js";
@@ -34,17 +35,17 @@ const CARGO_SEGMENT_LOAD_CONCURRENCY = 8;
34
35
  /** Storage-object lanes for one download set. Tiny objects use the wide
35
36
  * request window, while the separate byte budget narrows large chunks so
36
37
  * request concurrency can never silently become an unbounded memory claim. */
37
- const DIRECT_OBJECT_DOWNLOAD_CONCURRENCY = 128;
38
+ /** Admission is sized to the refs batch law so one same-turn wave coalesces
39
+ * into one refs frame and the span fetcher merges each pack's members into a
40
+ * few broad ranged reads. Memory is bounded by the byte laws below, never by
41
+ * this count. */
42
+ const DIRECT_OBJECT_DOWNLOAD_CONCURRENCY = CONTENT_REFS_BATCH_OBJECTS;
38
43
  // Opening one protected object transiently retains the fetched envelope,
39
44
  // WebCrypto's ciphertext-with-tag input, and the plaintext. Bound plaintext
40
45
  // to two full blocks so those three representations stay below 96 MiB even
41
46
  // on the 1 GiB runtimes used for ordinary Files materialization. Small
42
47
  // objects still use the full request-concurrency window.
43
48
  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
49
  const DETECT_QUIET_MS = 12;
49
50
  const DETECT_MAX_DEFERRAL_MS = 75;
50
51
  const DETECT_RETRY_MS = 250;
@@ -94,6 +95,9 @@ export class UserGroundHost {
94
95
  watchDirty = false;
95
96
  cloudState = null;
96
97
  converged = false;
98
+ /** Cumulative graph-page reads, reported as diffs on the stages that
99
+ * materialize roots so the ledger shows what each operation actually read. */
100
+ graphPageReads = { fetchedPages: 0, fetchedBytes: 0, cachedPages: 0 };
97
101
  syncing = null;
98
102
  recordRail = null;
99
103
  recordStore = null;
@@ -159,7 +163,7 @@ export class UserGroundHost {
159
163
  const local = this.localPort(this.activeIdentity);
160
164
  const result = await convergeUserGround(this.activeIdentity, {
161
165
  sha256Hex,
162
- cloud: this.cloudPort(this.activeIdentity),
166
+ cloud: this.cloudPort(this.activeIdentity, { kind: "core" }),
163
167
  local,
164
168
  });
165
169
  await this.resumeWorkspaceAdds(this.activeIdentity);
@@ -302,13 +306,16 @@ export class UserGroundHost {
302
306
  });
303
307
  const add = {
304
308
  sha256Hex,
305
- lookupRegistry: async () => {
309
+ lookupRegistry: async (workspaceId) => {
306
310
  const started = performance.now();
307
311
  this.stage({
308
312
  primitive: "download", stage: "registry-lookup", status: "started",
309
313
  });
314
+ const pagesBefore = { ...this.graphPageReads };
315
+ const planeBefore = this.direct.metrics();
310
316
  const resourceId = privateEntityResourceId(identity.userId, sha256Hex);
311
- const cloud = await this.cloudPort(identity).lookup(resourceId);
317
+ const cloud = await this.cloudPort(identity, { kind: "workspace", workspaceId })
318
+ .lookup(resourceId);
312
319
  if (!cloud)
313
320
  throw new Error("this user has no cloud entity registry");
314
321
  // The authority's active snapshot is the compact base; Add's active
@@ -322,7 +329,7 @@ export class UserGroundHost {
322
329
  if (deliveryWatermark < cloud.deliveryWatermark) {
323
330
  throw new Error("local delivery cursor is behind the active snapshot watermark");
324
331
  }
325
- const snapshot = snapshotFromRecords(recordsAtDeliveryPrefix(cloud.snapshot.records, readAcceptedResults(this.databasePath(identity), resourceId, cloud.deliveryWatermark, deliveryWatermark)));
332
+ const snapshot = snapshotFromRecords(await this.foldScopedPrefix(cloud.snapshot.records, readAcceptedResults(this.databasePath(identity), resourceId, cloud.deliveryWatermark, deliveryWatermark), workspaceId));
326
333
  const current = {
327
334
  ...cloud,
328
335
  deliveryWatermark,
@@ -334,6 +341,13 @@ export class UserGroundHost {
334
341
  stage: "registry-lookup",
335
342
  status: "completed",
336
343
  durationMs: performance.now() - started,
344
+ measurements: {
345
+ records: snapshot.records.length,
346
+ fetchedPages: this.graphPageReads.fetchedPages - pagesBefore.fetchedPages,
347
+ fetchedPageBytes: this.graphPageReads.fetchedBytes - pagesBefore.fetchedBytes,
348
+ cachedPages: this.graphPageReads.cachedPages - pagesBefore.cachedPages,
349
+ ...physicalPlaneDelta(planeBefore, this.direct.metrics()),
350
+ },
337
351
  });
338
352
  return current;
339
353
  },
@@ -564,12 +578,29 @@ export class UserGroundHost {
564
578
  throw stageError("Watch coverage", error);
565
579
  }
566
580
  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"
581
+ // Both proofs are root-scoped reads: the boundary's own record from
582
+ // whichever root holds it, and its durable reference from the core root
583
+ // — the only root references lawfully live in.
584
+ const hasRegisteredBoundary = async () => {
585
+ const graph = this.cloudState?.graph;
586
+ if (!graph)
587
+ return false;
588
+ const boundaryRoot = await graph.locate(workspaceId);
589
+ if (boundaryRoot === null)
590
+ return false;
591
+ const boundary = (await graph.recordsFor(boundaryRoot))
592
+ .find((record) => record.uuid === workspaceId);
593
+ if (!boundary || boundary.status !== "active")
594
+ return false;
595
+ const coreRoot = await this.coreRootId(identity, graph);
596
+ if (coreRoot === null)
597
+ return false;
598
+ return (await graph.recordsFor(coreRoot)).some((record) => record.type === "reference"
569
599
  && record.status === "active"
570
600
  && record.payloadVersion === workspaceId);
601
+ };
571
602
  if (hasRegisteredBoundaryInDatabase(database, workspaceId)
572
- && hasRegisteredBoundary(this.cloudState.graph.records())) {
603
+ && await hasRegisteredBoundary()) {
573
604
  // Already declared — but Register may only return once cargo closure
574
605
  // holds under the current content contract. The graph head can be
575
606
  // unchanged while a contract cutover moved its cargo namespace, so
@@ -751,7 +782,7 @@ export class UserGroundHost {
751
782
  if (!hasRegisteredBoundaryInDatabase(database, workspaceId)) {
752
783
  throw stageError("entity registration", new Error(`workspace ${workspaceId} and its durable reference were not recorded`));
753
784
  }
754
- if (!hasRegisteredBoundary(this.cloudState.graph.records())) {
785
+ if (!(await hasRegisteredBoundary())) {
755
786
  throw stageError("cloud graph commit", new Error(`workspace ${workspaceId} or its durable reference is absent from the committed head`));
756
787
  }
757
788
  return {
@@ -1128,7 +1159,7 @@ export class UserGroundHost {
1128
1159
  cacheDir(identity) {
1129
1160
  return join(liveMachineStateDir(this.userRoot(identity), identity.deviceId), "content");
1130
1161
  }
1131
- cloudPort(identity) {
1162
+ cloudPort(identity, scope) {
1132
1163
  return {
1133
1164
  lookup: async (resourceId) => {
1134
1165
  await this.publishPendingSnapshotsBeforeLookup(identity, resourceId);
@@ -1139,7 +1170,8 @@ export class UserGroundHost {
1139
1170
  }, ["private.resource.ready", "private.resource.missing"]);
1140
1171
  if (frame.type === "private.resource.missing")
1141
1172
  return null;
1142
- const ready = readyFromFrame(frame, await this.materializeRegistry(identity, frame));
1173
+ const graph = this.materializeRegistry(identity, frame);
1174
+ const ready = readyFromFrame(frame, graph, await this.selectScope(identity, graph, scope));
1143
1175
  this.cloudState = ready.state;
1144
1176
  return ready.cloud;
1145
1177
  },
@@ -1154,7 +1186,8 @@ export class UserGroundHost {
1154
1186
  snapshot_checksum: input.checksum,
1155
1187
  snapshot_bytes: bytes.length,
1156
1188
  }, ["private.resource.ready"]);
1157
- const ready = readyFromFrame(frame, await this.materializeRegistry(identity, frame));
1189
+ const graph = this.materializeRegistry(identity, frame);
1190
+ const ready = readyFromFrame(frame, graph, await this.selectScope(identity, graph, scope));
1158
1191
  const current = ready.cloud;
1159
1192
  this.cloudState = ready.state;
1160
1193
  const created = frame.duplicate === false;
@@ -1164,60 +1197,103 @@ export class UserGroundHost {
1164
1197
  },
1165
1198
  };
1166
1199
  }
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) {
1200
+ /** The authority's stored snapshot as this machine's graph. A head is held
1201
+ * lazily — no page is read until an operation asks for a root — and every
1202
+ * page this machine already cached is a local read, so an unchanged root
1203
+ * never crosses the network twice. */
1204
+ materializeRegistry(identity, frame) {
1171
1205
  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`);
1206
+ return registryGraphFromSnapshot(Buffer.from(String(frame.snapshot_b64 || ""), "base64"), (ref) => this.readGraphPage(identity, resourceId, ref), sha256Hex);
1207
+ }
1208
+ /** The records a lookup hands to Live: every root's own record — the
1209
+ * discovery set that proves root uniqueness — plus the full records of
1210
+ * exactly the roots the operation acts on. A records-shaped snapshot is a
1211
+ * first publication's own small graph and travels whole, keeping its
1212
+ * records-shape checksum law intact. */
1213
+ async selectScope(identity, graph, scope) {
1214
+ if (graph.head === null)
1215
+ return graph.recordsShapedSnapshot().records;
1216
+ if (scope.kind === "none")
1217
+ return [];
1218
+ const selected = new Set();
1219
+ const core = await this.coreRootId(identity, graph);
1220
+ if (core)
1221
+ selected.add(core);
1222
+ if (scope.kind === "workspace") {
1223
+ const boundaryRoot = await graph.locate(scope.workspaceId);
1224
+ if (boundaryRoot)
1225
+ selected.add(boundaryRoot);
1226
+ }
1227
+ const records = new Map((await graph.rootRecords())
1228
+ .map((record) => [record.uuid, record]));
1229
+ for (const rootId of selected) {
1230
+ for (const record of await graph.recordsFor(rootId))
1231
+ records.set(record.uuid, record);
1232
+ }
1233
+ return [...records.values()];
1234
+ }
1235
+ /** The account's core root: the one active top-level workspace named by the
1236
+ * account email. Registered-boundary references live only there. */
1237
+ async coreRootId(identity, graph) {
1238
+ const email = identity.userEmail.toLowerCase();
1239
+ const core = (await graph.rootRecords()).find((record) => record.status === "active"
1240
+ && record.parentUUID === null
1241
+ && record.type === "workspace"
1242
+ && record.name.toLowerCase() === email);
1243
+ return core?.uuid ?? null;
1244
+ }
1245
+ /** Fold the committed delivery tail over a root-scoped base without losing
1246
+ * a subtree the tail moved across roots. A container the folded boundary
1247
+ * contains only through delivered results may still file unchanged
1248
+ * descendants under the root that used to hold it, so that root's records
1249
+ * join the base and the fold reruns until no probe widens it. Leaf results
1250
+ * travel whole and never probe. */
1251
+ async foldScopedPrefix(base, accepted, boundaryId) {
1252
+ if (accepted.length === 0)
1253
+ return base;
1254
+ const graph = this.cloudState?.graph;
1255
+ if (!graph)
1256
+ throw new Error("cloud state is unavailable for a registry lookup");
1257
+ const probed = new Set(base.map((record) => record.uuid));
1258
+ let widened = base;
1259
+ for (;;) {
1260
+ const folded = recordsAtDeliveryPrefix(widened, accepted);
1261
+ const probes = containedRecords(folded, boundaryId)
1262
+ .filter((record) => !probed.has(record.uuid)
1263
+ && (record.type === "workspace" || record.type === "folder" || record.type === "repo.git"))
1264
+ .map((record) => record.uuid);
1265
+ if (probes.length === 0)
1266
+ return folded;
1267
+ for (const uuid of probes)
1268
+ probed.add(uuid);
1269
+ const located = await graph.membership(new Set(probes));
1270
+ const merged = new Map(widened.map((record) => [record.uuid, record]));
1271
+ for (const rootId of new Set(located.values())) {
1272
+ for (const record of await graph.recordsFor(rootId)) {
1273
+ merged.set(record.uuid, record);
1274
+ }
1204
1275
  }
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;
1276
+ if (merged.size === widened.length)
1277
+ return folded;
1278
+ widened = [...merged.values()];
1279
+ }
1280
+ }
1281
+ /** Read one graph page by identity: cached pages come off local disk, the
1282
+ * rest are fetched straight from content storage and cached on the way in.
1283
+ * The graph proves every page against its declared identity as it parses,
1284
+ * so this reader only guards the cache against torn copies. */
1285
+ async readGraphPage(identity, resourceId, ref) {
1286
+ const path = join(graphPageCacheDir(this.cacheDir(identity)), `${ref.sha256}.bin`);
1287
+ const cached = readVerifiedRegistryPage(path, ref, sha256Hex);
1288
+ if (cached) {
1289
+ this.graphPageReads.cachedPages += 1;
1290
+ return cached;
1291
+ }
1292
+ const bytes = await retryContent(() => this.direct.fetchObject(resourceId, { kind: "block", sha256: ref.sha256 }), true);
1293
+ writeVerifiedRegistryPage(path, ref, bytes, sha256Hex);
1294
+ this.graphPageReads.fetchedPages += 1;
1295
+ this.graphPageReads.fetchedBytes += bytes.byteLength;
1296
+ return bytes;
1221
1297
  }
1222
1298
  localPort(identity) {
1223
1299
  const userRoot = this.userRoot(identity);
@@ -1604,11 +1680,13 @@ export class UserGroundHost {
1604
1680
  const unique = new Map(wants.map((want) => [want.artifact.contentHash, want]));
1605
1681
  const sole = unique.size === 1 ? [...unique.values()][0]?.artifact : undefined;
1606
1682
  const started = performance.now();
1683
+ const planeBefore = this.direct.metrics();
1607
1684
  let declaredBytes = 0;
1608
1685
  let downloadedBytes = 0;
1609
1686
  let downloadedChunks = 0;
1610
1687
  let reusedChunks = 0;
1611
1688
  let requests = 0;
1689
+ let cacheWriteMs = 0;
1612
1690
  const entityId = trace?.entityId ?? sole?.entityId;
1613
1691
  const identifiers = {
1614
1692
  ...(trace?.workspaceId ? { workspaceId: trace.workspaceId } : {}),
@@ -1673,10 +1751,14 @@ export class UserGroundHost {
1673
1751
  return fetchObject({ kind: "block", sha256: chunk.sha256 });
1674
1752
  },
1675
1753
  putChunk: async (artifact, _manifest, index, bytes) => {
1754
+ const writeStarted = performance.now();
1676
1755
  await target(artifact).putChunk(index, bytes);
1756
+ cacheWriteMs += performance.now() - writeStarted;
1677
1757
  },
1678
1758
  sealArtifact: async (artifact) => {
1759
+ const sealStarted = performance.now();
1679
1760
  const sealed = await target(artifact).seal();
1761
+ cacheWriteMs += performance.now() - sealStarted;
1680
1762
  return { local: sealed, bytes: sealed.bytes, contentHash: sealed.contentHash };
1681
1763
  },
1682
1764
  }, {
@@ -1704,7 +1786,12 @@ export class UserGroundHost {
1704
1786
  downloadedBytes,
1705
1787
  downloadedChunks,
1706
1788
  reusedChunks,
1789
+ // `requests` is logical object reads; the plane deltas below are
1790
+ // the physical facts — actual storage GETs, sealed bytes on the
1791
+ // wire, refs round-trips, decrypt and cache-write CPU.
1707
1792
  requests,
1793
+ ...physicalPlaneDelta(planeBefore, this.direct.metrics()),
1794
+ cacheWriteMs: Math.round(cacheWriteMs),
1708
1795
  },
1709
1796
  });
1710
1797
  return content;
@@ -2124,7 +2211,8 @@ export class UserGroundHost {
2124
2211
  // the roots it rebuilt — so the machine re-reads the authority's head
2125
2212
  // before deriving the next boundary on top of it.
2126
2213
  if (!this.cloudState) {
2127
- await this.cloudPort(identity).lookup(privateEntityResourceId(identity.userId, sha256Hex));
2214
+ await this.cloudPort(identity, { kind: "none" })
2215
+ .lookup(privateEntityResourceId(identity.userId, sha256Hex));
2128
2216
  }
2129
2217
  const state = this.cloudState;
2130
2218
  if (!state)
@@ -2155,6 +2243,12 @@ export class UserGroundHost {
2155
2243
  leg[`${name}Ms`] = performance.now() - started;
2156
2244
  return value;
2157
2245
  };
2246
+ const timedAsync = async (name, work) => {
2247
+ const started = performance.now();
2248
+ const value = await work();
2249
+ leg[`${name}Ms`] = performance.now() - started;
2250
+ return value;
2251
+ };
2158
2252
  if (trace?.journey === "register") {
2159
2253
  this.stage({
2160
2254
  primitive: "register",
@@ -2167,7 +2261,7 @@ export class UserGroundHost {
2167
2261
  .map(portableRecord), readGroundRowsForRoots(this.databasePath(identity), "detection_notebook", publicationRootIds)
2168
2262
  .map(portableRecord)));
2169
2263
  const accepted = timed("officialRows", () => readAcceptedResults(this.databasePath(identity), state.resourceId, state.deliveryWatermark, deliveryWatermark));
2170
- const rebuilt = timed("merge", () => publicationRoots(state.graph, localRecords, accepted));
2264
+ const rebuilt = await timedAsync("merge", () => publicationRoots(state.graph, localRecords, accepted));
2171
2265
  const projected = timed("project", () => new Map([...rebuilt].map(([rootId, records]) => [rootId, records.length === 0 ? records : travelingRecords(records, sha256Hex)])));
2172
2266
  const traveling = [...projected.values()].flat();
2173
2267
  const impossibleGitLeaf = traveling.find((record) => (record.type === "file.text" || record.type === "file.binary" || record.type === "link")
@@ -2179,7 +2273,7 @@ export class UserGroundHost {
2179
2273
  // ONE canonical serialization per rebuilt root: the page bytes are the
2180
2274
  // journal's payload, the upload's payload, and the head's identities.
2181
2275
  // Untouched roots are not serialized, hashed, or journaled at all.
2182
- const publication = timed("pages", () => state.graph.rebuild(projected, sha256Hex));
2276
+ const publication = timed("pages", () => state.graph.rebuild(projected));
2183
2277
  const checksum = publication.checksum;
2184
2278
  const localIds = new Set(localRecords.map((record) => record.uuid));
2185
2279
  // Graph membership is never proof of sealed cargo: a record in the cloud
@@ -5177,7 +5271,7 @@ function recordRootResolver(records) {
5177
5271
  * touched is rebuilt from the committed graph with those results folded in, so
5178
5272
  * the published graph never declares a watermark it has not actually applied.
5179
5273
  * Every root outside this map keeps its committed pages untouched. */
5180
- function publicationRoots(graph, localRecords, accepted) {
5274
+ async function publicationRoots(graph, localRecords, accepted) {
5181
5275
  const rebuilt = new Map();
5182
5276
  const walked = new Map(localRecords
5183
5277
  .filter((record) => record.parentUUID === null)
@@ -5194,9 +5288,16 @@ function publicationRoots(graph, localRecords, accepted) {
5194
5288
  for (const [rootId, records] of walked)
5195
5289
  rebuilt.set(rootId, records);
5196
5290
  }
5291
+ // One page-bounded sweep answers every committed-root question this merge
5292
+ // asks: which root holds each accepted result, and which root still files
5293
+ // each record the walk re-rooted.
5294
+ const located = await graph.membership(new Set([
5295
+ ...accepted.map((result) => result.uuid),
5296
+ ...localRecords.map((record) => record.uuid),
5297
+ ]));
5197
5298
  const overlays = new Map();
5198
5299
  for (const result of accepted) {
5199
- const rootId = graph.rootOf(result.uuid);
5300
+ const rootId = located.get(result.uuid) ?? null;
5200
5301
  if (rootId === null || walked.has(rootId))
5201
5302
  continue;
5202
5303
  const overlay = overlays.get(rootId);
@@ -5206,7 +5307,8 @@ function publicationRoots(graph, localRecords, accepted) {
5206
5307
  overlays.set(rootId, [result]);
5207
5308
  }
5208
5309
  for (const [rootId, results] of overlays) {
5209
- const byUuid = new Map(graph.recordsFor(rootId).map((record) => [record.uuid, record]));
5310
+ const byUuid = new Map((await graph.recordsFor(rootId))
5311
+ .map((record) => [record.uuid, record]));
5210
5312
  for (const result of results)
5211
5313
  byUuid.set(result.uuid, result);
5212
5314
  // Entity channels are independently ordered, so container membership is
@@ -5219,7 +5321,7 @@ function publicationRoots(graph, localRecords, accepted) {
5219
5321
  if (graph.head === null) {
5220
5322
  for (const rootId of graph.rootIds()) {
5221
5323
  if (!rebuilt.has(rootId))
5222
- rebuilt.set(rootId, graph.recordsFor(rootId));
5324
+ rebuilt.set(rootId, await graph.recordsFor(rootId));
5223
5325
  }
5224
5326
  }
5225
5327
  // Re-rooting is a move, not a copy: an enclosing Register walks records the
@@ -5228,7 +5330,7 @@ function publicationRoots(graph, localRecords, accepted) {
5228
5330
  // with no records at all — the rebuild then drops it from the graph.
5229
5331
  const vacated = new Map();
5230
5332
  for (const record of localRecords) {
5231
- const rootId = graph.rootOf(record.uuid);
5333
+ const rootId = located.get(record.uuid) ?? null;
5232
5334
  if (rootId === null || walked.has(rootId))
5233
5335
  continue;
5234
5336
  const gone = vacated.get(rootId);
@@ -5238,7 +5340,7 @@ function publicationRoots(graph, localRecords, accepted) {
5238
5340
  vacated.set(rootId, new Set([record.uuid]));
5239
5341
  }
5240
5342
  for (const [rootId, gone] of vacated) {
5241
- const held = rebuilt.get(rootId) ?? graph.recordsFor(rootId);
5343
+ const held = rebuilt.get(rootId) ?? await graph.recordsFor(rootId);
5242
5344
  rebuilt.set(rootId, held.filter((record) => !gone.has(record.uuid)));
5243
5345
  }
5244
5346
  return rebuilt;
@@ -5814,13 +5916,15 @@ async function installCloudWorkspace(input) {
5814
5916
  alreadyMaterialized: false,
5815
5917
  };
5816
5918
  }
5817
- function readyFromFrame(frame, graph) {
5919
+ /** The authority's ready frame as Live sees it. The snapshot carries only the
5920
+ * records the caller's scope selected — never a flattened whole account. */
5921
+ function readyFromFrame(frame, graph, records) {
5818
5922
  const cloud = {
5819
5923
  resourceId: String(frame.resource_id || ""),
5820
5924
  version: Number(frame.head_version),
5821
5925
  deliveryWatermark: Number(frame.delivery_watermark),
5822
5926
  checksum: String(frame.snapshot_checksum || ""),
5823
- snapshot: graph.snapshot(),
5927
+ snapshot: snapshotFromRecords(records),
5824
5928
  ...(graph.head ? { head: graph.head } : {}),
5825
5929
  };
5826
5930
  return {
@@ -5835,6 +5939,18 @@ function readyFromFrame(frame, graph) {
5835
5939
  },
5836
5940
  };
5837
5941
  }
5942
+ /** What one leg physically did on the content plane: the counter movement
5943
+ * between its start and end, ms rounded for the ledger. */
5944
+ function physicalPlaneDelta(before, after) {
5945
+ return {
5946
+ rangedGets: after.rangedGets - before.rangedGets,
5947
+ wholeGets: after.wholeGets - before.wholeGets,
5948
+ fetchedSealedBytes: after.fetchedBytes - before.fetchedBytes,
5949
+ refsFrames: after.refsFrames - before.refsFrames,
5950
+ refsMs: Math.round(after.refsMs - before.refsMs),
5951
+ decryptMs: Math.round(after.decryptMs - before.decryptMs),
5952
+ };
5953
+ }
5838
5954
  /** Immutable content reads and writes are idempotent. Retry transient wire or
5839
5955
  * authority failures; reads additionally tolerate the brief not-found window
5840
5956
  * between an immutable object acknowledgement and globally visible R2 state. */