@amalgm/shell 0.1.130 → 0.1.132

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.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Per-leg accounting for one content transfer.
3
+ *
4
+ * Every instrumented operation lands its wall time in exactly one named leg.
5
+ * Legs sum busy time across concurrent operations, so under a wide window
6
+ * their total may exceed the transfer's wall clock; `idleMs` is the opposite
7
+ * fact — wall time in which zero instrumented operations were in flight — so
8
+ * a transfer's ledger reads as legs (where work went) plus idle (where
9
+ * nothing went). Externally measured durations (a gateway acknowledgement's
10
+ * own timings) fold in without in-flight tracking: they decompose a timed
11
+ * leg, they do not extend it.
12
+ */
13
+ export declare class TransferLegs {
14
+ private readonly totals;
15
+ private inFlight;
16
+ private quietSince;
17
+ private idleMs;
18
+ /** Fold an externally measured duration into a leg. Absent or non-positive
19
+ * values contribute nothing, so wire frames may omit their timings. */
20
+ add(leg: string, ms: number): void;
21
+ time<T>(leg: string, work: () => Promise<T>): Promise<T>;
22
+ timeSync<T>(leg: string, work: () => T): T;
23
+ /** Rounded per-leg totals plus `idleMs` through this moment, ready to
24
+ * spread into stage measurements. */
25
+ report(): Record<string, number>;
26
+ private enter;
27
+ private exit;
28
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Per-leg accounting for one content transfer.
3
+ *
4
+ * Every instrumented operation lands its wall time in exactly one named leg.
5
+ * Legs sum busy time across concurrent operations, so under a wide window
6
+ * their total may exceed the transfer's wall clock; `idleMs` is the opposite
7
+ * fact — wall time in which zero instrumented operations were in flight — so
8
+ * a transfer's ledger reads as legs (where work went) plus idle (where
9
+ * nothing went). Externally measured durations (a gateway acknowledgement's
10
+ * own timings) fold in without in-flight tracking: they decompose a timed
11
+ * leg, they do not extend it.
12
+ */
13
+ export class TransferLegs {
14
+ totals = new Map();
15
+ inFlight = 0;
16
+ quietSince = performance.now();
17
+ idleMs = 0;
18
+ /** Fold an externally measured duration into a leg. Absent or non-positive
19
+ * values contribute nothing, so wire frames may omit their timings. */
20
+ add(leg, ms) {
21
+ if (!Number.isFinite(ms) || ms <= 0)
22
+ return;
23
+ this.totals.set(leg, (this.totals.get(leg) ?? 0) + ms);
24
+ }
25
+ async time(leg, work) {
26
+ const startedAt = this.enter();
27
+ try {
28
+ return await work();
29
+ }
30
+ finally {
31
+ this.exit(leg, startedAt);
32
+ }
33
+ }
34
+ timeSync(leg, work) {
35
+ const startedAt = this.enter();
36
+ try {
37
+ return work();
38
+ }
39
+ finally {
40
+ this.exit(leg, startedAt);
41
+ }
42
+ }
43
+ /** Rounded per-leg totals plus `idleMs` through this moment, ready to
44
+ * spread into stage measurements. */
45
+ report() {
46
+ const now = performance.now();
47
+ const trailingIdle = this.inFlight === 0 ? now - this.quietSince : 0;
48
+ return {
49
+ ...Object.fromEntries([...this.totals].map(([leg, ms]) => [leg, Math.round(ms)])),
50
+ idleMs: Math.round(this.idleMs + trailingIdle),
51
+ };
52
+ }
53
+ enter() {
54
+ const now = performance.now();
55
+ if (this.inFlight === 0)
56
+ this.idleMs += now - this.quietSince;
57
+ this.inFlight += 1;
58
+ return now;
59
+ }
60
+ exit(leg, startedAt) {
61
+ const now = performance.now();
62
+ this.add(leg, now - startedAt);
63
+ this.inFlight -= 1;
64
+ if (this.inFlight === 0)
65
+ this.quietSince = now;
66
+ }
67
+ }
68
+ //# sourceMappingURL=transfer-instrumentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer-instrumentation.js","sourceRoot":"","sources":["../src/transfer-instrumentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAY;IACN,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,QAAQ,GAAG,CAAC,CAAC;IACb,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAC/B,MAAM,GAAG,CAAC,CAAC;IAEnB;2EACuE;IACvE,GAAG,CAAC,GAAW,EAAE,EAAU;QACzB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;YAAE,OAAO;QAC5C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,IAAI,CAAI,GAAW,EAAE,IAAsB;QAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,QAAQ,CAAI,GAAW,EAAE,IAAa;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED;yCACqC;IACrC,MAAM;QACJ,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACrE,OAAO;YACL,GAAG,MAAM,CAAC,WAAW,CACnB,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAC3D;YACD,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC;SAC/C,CAAC;IACJ,CAAC;IAEO,KAAK;QACX,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC;YAAE,IAAI,CAAC,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;QAC9D,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACnB,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,IAAI,CAAC,GAAW,EAAE,SAAiB;QACzC,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,SAAS,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QACnB,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC;YAAE,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;IACjD,CAAC;CACF"}
@@ -221,7 +221,9 @@ export declare class UserGroundHost {
221
221
  /** One self-contained cargo segment on the wire: declared artifacts in
222
222
  * header order, their bytes concatenated behind them, one verified round
223
223
  * trip. Returns the authority's sealed hashes for the stream law to
224
- * prove coverage. */
224
+ * prove coverage. The acknowledgement carries the gateway's own leg
225
+ * timings — verification, storage, sealing — which fold into the ledger
226
+ * as a decomposition of this round trip's `wireMs`. */
225
227
  private putCargo;
226
228
  private uploadSnapshotContent;
227
229
  /** Download a set of immutable artifacts straight from content storage.
@@ -15,6 +15,7 @@ import { ContentCacheDownloadSet, captureContentFile, hasSealedContent, hashCont
15
15
  import { StagedContentSet, addStagingEvidenceDir, clearAddStagingEvidence, } from "./workspace-add-staging.js";
16
16
  import { DirectContentPlane } from "./content-direct.js";
17
17
  import { encodeContentWireBytes } from "./content-wire-codec.js";
18
+ import { TransferLegs } from "./transfer-instrumentation.js";
18
19
  import { exactTextReplay, planGroundDetection, reconcileGroundUUIDs, } from "./detection/portable.js";
19
20
  import { AcceptedInboxResultIndex } from "./detection/accepted-result.js";
20
21
  import { sameNotebookBasisRow } from "./detection/notebook-basis.js";
@@ -1391,17 +1392,22 @@ export class UserGroundHost {
1391
1392
  /** One self-contained cargo segment on the wire: declared artifacts in
1392
1393
  * header order, their bytes concatenated behind them, one verified round
1393
1394
  * trip. Returns the authority's sealed hashes for the stream law to
1394
- * prove coverage. */
1395
- async putCargo(resourceId, artifacts, payload) {
1396
- const encoded = await encodeContentWireBytes(payload);
1397
- const frame = await retryContent(() => this.wire.requestBinary({
1395
+ * prove coverage. The acknowledgement carries the gateway's own leg
1396
+ * timings — verification, storage, sealing — which fold into the ledger
1397
+ * as a decomposition of this round trip's `wireMs`. */
1398
+ async putCargo(resourceId, artifacts, payload, legs) {
1399
+ const encoded = await legs.time("encodeMs", () => encodeContentWireBytes(payload));
1400
+ const frame = await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1398
1401
  type: "private.entity-content.put-cargo",
1399
1402
  resource_id: resourceId,
1400
1403
  artifacts,
1401
1404
  bytes: payload.byteLength,
1402
1405
  wire_bytes: encoded.bytes.byteLength,
1403
1406
  ...(encoded.encoding ? { content_encoding: encoded.encoding } : {}),
1404
- }, encoded.bytes, ["private.entity-content.stored-cargo"]));
1407
+ }, encoded.bytes, ["private.entity-content.stored-cargo"])));
1408
+ legs.add("gatewayVerifyMs", Number(frame.verify_ms));
1409
+ legs.add("gatewayStoreMs", Number(frame.store_ms));
1410
+ legs.add("gatewaySealMs", Number(frame.seal_ms));
1405
1411
  return {
1406
1412
  sealed: Array.isArray(frame.sealed) ? frame.sealed.map(String) : [],
1407
1413
  wireBytes: encoded.bytes.byteLength,
@@ -1417,9 +1423,12 @@ export class UserGroundHost {
1417
1423
  }
1418
1424
  }
1419
1425
  const started = performance.now();
1426
+ const legs = new TransferLegs();
1427
+ const timedSha256Hex = (input) => legs.timeSync("hashMs", () => sha256Hex(input));
1420
1428
  let uploadedChunks = 0;
1421
1429
  let reusedChunks = 0;
1422
1430
  let uploadedBytes = 0;
1431
+ let loadedBytes = 0;
1423
1432
  let inventoryRequests = 0;
1424
1433
  const identifiers = {
1425
1434
  ...(trace?.workspaceId ? { workspaceId: trace.workspaceId } : {}),
@@ -1440,16 +1449,16 @@ export class UserGroundHost {
1440
1449
  // upload — asked before any source is touched, so sealed artifacts cost
1441
1450
  // zero local reads and a retried publication whose upload already sealed
1442
1451
  // completely never needs its captured ground to still exist.
1443
- const alreadySealed = await this.sealedContentHashes(resourceId, [...byHash.keys()]);
1452
+ const alreadySealed = await legs.time("planMs", () => this.sealedContentHashes(resourceId, [...byHash.keys()]));
1444
1453
  const unsealed = [...byHash.values()]
1445
1454
  .filter(({ artifact }) => !alreadySealed.has(artifact.contentHash));
1446
- const rows = readGroundRowsByUuid(this.databasePath(identity), unsealed.map(({ uuid }) => uuid));
1455
+ const rows = legs.timeSync("planMs", () => readGroundRowsByUuid(this.databasePath(identity), unsealed.map(({ uuid }) => uuid)));
1447
1456
  // Each artifact uploads from the strongest evidence available: a cache
1448
1457
  // copy when another journey pinned one, otherwise the ground entity
1449
1458
  // itself. Register stages nothing, so the ground path is the common
1450
1459
  // case — and every read is re-proven against the captured hash, turning
1451
1460
  // ground churn into a stale capture instead of a wrong upload.
1452
- const sources = unsealed.map(({ artifact, uuid }) => {
1461
+ const sources = legs.timeSync("planMs", () => unsealed.map(({ artifact, uuid }) => {
1453
1462
  const cached = contentCacheFile(cache, artifact.contentHash);
1454
1463
  const fromCache = existsSync(cached);
1455
1464
  const row = rows.get(uuid);
@@ -1489,7 +1498,7 @@ export class UserGroundHost {
1489
1498
  ...(declared && declared.bytes === bytes ? { manifest: declared } : {}),
1490
1499
  bytes,
1491
1500
  };
1492
- });
1501
+ }));
1493
1502
  const stale = (source, error) => {
1494
1503
  if (source.fromCache) {
1495
1504
  // A cache copy that no longer proves its hash is torn evidence:
@@ -1513,10 +1522,11 @@ export class UserGroundHost {
1513
1522
  const loaded = new Array(segment.items.length);
1514
1523
  await boundedForEach(segment.items.map((item, index) => ({ source: item.source, index })), CARGO_SEGMENT_LOAD_CONCURRENCY, async ({ source, index }) => {
1515
1524
  try {
1516
- const bytes = source.linkTarget ?? await readFile(source.file);
1525
+ const bytes = source.linkTarget ?? await legs.time("loadMs", () => readFile(source.file));
1526
+ loadedBytes += bytes.byteLength;
1517
1527
  loaded[index] = {
1518
1528
  bytes,
1519
- manifest: deriveUploadManifest(source.artifact, bytes, sha256Hex),
1529
+ manifest: deriveUploadManifest(source.artifact, bytes, timedSha256Hex),
1520
1530
  };
1521
1531
  }
1522
1532
  catch (error) {
@@ -1529,7 +1539,7 @@ export class UserGroundHost {
1529
1539
  content_hash: segment.items[index].contentHash,
1530
1540
  bytes: item.manifest.bytes,
1531
1541
  chunks: item.manifest.chunks.map((chunk) => ({ sha256: chunk.sha256, bytes: chunk.bytes })),
1532
- })), Buffer.concat(loaded.map((item) => item.bytes)));
1542
+ })), Buffer.concat(loaded.map((item) => item.bytes)), legs);
1533
1543
  for (const item of loaded)
1534
1544
  uploadedChunks += item.manifest.chunks.length;
1535
1545
  uploadedBytes += wireBytes;
@@ -1544,7 +1554,7 @@ export class UserGroundHost {
1544
1554
  const chunk = manifest.chunks[index];
1545
1555
  if (!chunk)
1546
1556
  throw new Error(`content manifest has no chunk ${index}`);
1547
- await retryContent(() => this.wire.requestBinary({
1557
+ await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1548
1558
  type: "private.entity-content.put",
1549
1559
  resource_id: resourceId,
1550
1560
  content_hash: artifact.contentHash,
@@ -1553,31 +1563,32 @@ export class UserGroundHost {
1553
1563
  part_count: manifest.chunks.length,
1554
1564
  sha256: chunk.sha256,
1555
1565
  bytes: chunk.bytes,
1556
- }, bytes, ["private.entity-content.stored"]));
1566
+ }, bytes, ["private.entity-content.stored"])));
1557
1567
  uploadedBytes += bytes.byteLength;
1558
1568
  };
1559
1569
  try {
1560
1570
  const receipt = await uploadArtifact(artifact, {
1561
1571
  bytes: fileBytes,
1562
1572
  ...(knownManifest ? { manifest: knownManifest, manifestVerified: true } : {}),
1563
- verify: async (contentHash) => await hashContentFile(file) === contentHash,
1573
+ verify: async (contentHash) => await legs.time("verifyMs", () => hashContentFile(file)) === contentHash,
1564
1574
  read: async (offset, bytes) => {
1565
1575
  sourceFile.handle ??= open(file, "r");
1566
1576
  const handle = await sourceFile.handle;
1567
1577
  const buffer = Buffer.allocUnsafe(bytes);
1568
- const { bytesRead } = await handle.read(buffer, 0, bytes, offset);
1578
+ const { bytesRead } = await legs.time("loadMs", () => handle.read(buffer, 0, bytes, offset));
1579
+ loadedBytes += bytesRead;
1569
1580
  return buffer.subarray(0, bytesRead);
1570
1581
  },
1571
1582
  }, {
1572
- sha256Hex,
1583
+ sha256Hex: timedSha256Hex,
1573
1584
  missingChunks: async (_candidate, manifest) => {
1574
1585
  inventoryRequests += 1;
1575
- const frame = await retryContent(() => this.wire.request({
1586
+ const frame = await legs.time("inventoryMs", () => retryContent(() => this.wire.request({
1576
1587
  type: "private.entity-content.inventory",
1577
1588
  resource_id: resourceId,
1578
1589
  content_hash: artifact.contentHash,
1579
1590
  chunks: manifest.chunks,
1580
- }, ["private.entity-content.inventory-result"]));
1591
+ }, ["private.entity-content.inventory-result"])));
1581
1592
  manifestPresent = frame.complete === true;
1582
1593
  if (!Array.isArray(frame.missing)) {
1583
1594
  throw new Error("content authority returned an invalid inventory");
@@ -1586,8 +1597,8 @@ export class UserGroundHost {
1586
1597
  },
1587
1598
  putChunk: async (_candidate, manifest, index, bytes) => putChunk(manifest, index, bytes),
1588
1599
  putChunkBatch: async (_candidate, manifest, indexes, bytes) => {
1589
- const encoded = await encodeContentWireBytes(bytes);
1590
- await retryContent(() => this.wire.requestBinary({
1600
+ const encoded = await legs.time("encodeMs", () => encodeContentWireBytes(bytes));
1601
+ await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1591
1602
  type: "private.entity-content.put-batch",
1592
1603
  resource_id: resourceId,
1593
1604
  content_hash: artifact.contentHash,
@@ -1600,14 +1611,14 @@ export class UserGroundHost {
1600
1611
  bytes: bytes.byteLength,
1601
1612
  wire_bytes: encoded.bytes.byteLength,
1602
1613
  ...(encoded.encoding ? { content_encoding: encoded.encoding } : {}),
1603
- }, encoded.bytes, ["private.entity-content.stored-batch"]));
1614
+ }, encoded.bytes, ["private.entity-content.stored-batch"])));
1604
1615
  uploadedBytes += encoded.bytes.byteLength;
1605
1616
  },
1606
1617
  putManifest: async (_candidate, manifest) => {
1607
1618
  if (manifestPresent)
1608
1619
  return;
1609
1620
  const bytes = Buffer.from(stableJson(manifest), "utf8");
1610
- await retryContent(() => this.wire.requestBinary({
1621
+ await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1611
1622
  type: "private.entity-content.put",
1612
1623
  resource_id: resourceId,
1613
1624
  content_hash: artifact.contentHash,
@@ -1616,7 +1627,7 @@ export class UserGroundHost {
1616
1627
  part_count: manifest.chunks.length,
1617
1628
  sha256: sha256Hex(bytes),
1618
1629
  bytes: bytes.length,
1619
- }, bytes, ["private.entity-content.stored"]));
1630
+ }, bytes, ["private.entity-content.stored"])));
1620
1631
  uploadedBytes += bytes.byteLength;
1621
1632
  },
1622
1633
  });
@@ -1654,10 +1665,18 @@ export class UserGroundHost {
1654
1665
  reusedArtifacts: byHash.size - sources.length,
1655
1666
  declaredBytes: sources.reduce((total, source) => total + source.bytes, 0),
1656
1667
  uploadedBytes,
1668
+ loadedBytes,
1657
1669
  uploadedChunks,
1658
1670
  reusedChunks,
1659
1671
  inventoryRequests,
1660
1672
  cargoSegments,
1673
+ // Where this upload's time went, leg by leg: plan (seal inventory
1674
+ // + ground rows + source stats), load (source reads), hash, verify
1675
+ // (fused re-read + hash of a hinted manifest's source), encode
1676
+ // (compression), wire (data round trips, whose gateway share the
1677
+ // acknowledgement timings decompose), inventory round trips, and
1678
+ // idle — wall time with nothing in flight.
1679
+ ...legs.report(),
1661
1680
  },
1662
1681
  });
1663
1682
  }
@@ -1668,7 +1687,7 @@ export class UserGroundHost {
1668
1687
  status: "failed",
1669
1688
  ...identifiers,
1670
1689
  durationMs: performance.now() - started,
1671
- measurements: { journey: trace?.journey ?? "bootstrap" },
1690
+ measurements: { journey: trace?.journey ?? "bootstrap", ...legs.report() },
1672
1691
  });
1673
1692
  throw error;
1674
1693
  }
@@ -1683,12 +1702,13 @@ export class UserGroundHost {
1683
1702
  const sole = unique.size === 1 ? [...unique.values()][0]?.artifact : undefined;
1684
1703
  const started = performance.now();
1685
1704
  const planeBefore = this.direct.metrics();
1705
+ const legs = new TransferLegs();
1706
+ const timedSha256Hex = (input) => legs.timeSync("hashMs", () => sha256Hex(input));
1686
1707
  let declaredBytes = 0;
1687
1708
  let downloadedBytes = 0;
1688
1709
  let downloadedChunks = 0;
1689
1710
  let reusedChunks = 0;
1690
1711
  let requests = 0;
1691
- let cacheWriteMs = 0;
1692
1712
  let stagedArtifacts = 0;
1693
1713
  const entityId = trace?.entityId ?? sole?.entityId;
1694
1714
  const identifiers = {
@@ -1724,19 +1744,19 @@ export class UserGroundHost {
1724
1744
  // for a fresh ref every attempt.
1725
1745
  const fetchObject = async (object) => {
1726
1746
  requests += 1;
1727
- const bytes = await retryContent(() => this.direct.fetchObject(resourceId, object), true, retryAttempts);
1747
+ const bytes = await legs.time("fetchMs", () => retryContent(() => this.direct.fetchObject(resourceId, object), true, retryAttempts));
1728
1748
  downloadedBytes += bytes.byteLength;
1729
1749
  return bytes;
1730
1750
  };
1731
1751
  try {
1732
1752
  const receipts = await downloadAllArtifacts([...unique.values()].map((want) => want.artifact), {
1733
- sha256Hex,
1753
+ sha256Hex: timedSha256Hex,
1734
1754
  getManifest: async (artifact) => {
1735
1755
  // The record's plan or this machine's own manifest cache already
1736
1756
  // proves chunk identity; downloaded bytes are still hash-verified,
1737
1757
  // so a stale local manifest can only fail loudly, never corrupt.
1738
- const local = unique.get(artifact.contentHash)?.manifest
1739
- ?? readCachedManifest(cacheDir, artifact.contentHash);
1758
+ const local = legs.timeSync("planMs", () => unique.get(artifact.contentHash)?.manifest
1759
+ ?? readCachedManifest(cacheDir, artifact.contentHash));
1740
1760
  const manifest = local ?? await (async () => {
1741
1761
  const bytes = await fetchObject({ kind: "manifest", sha256: artifact.contentHash });
1742
1762
  const checked = checkContentManifest(JSON.parse(bytes.toString("utf8")), artifact.contentHash);
@@ -1748,32 +1768,28 @@ export class UserGroundHost {
1748
1768
  // destination — except content this machine already holds sealed,
1749
1769
  // which stays on the cache rail so a warm re-Add remains a local
1750
1770
  // 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
- }
1771
+ legs.timeSync("planMs", () => {
1772
+ const stagedPaths = stagedSet && staged?.destinations.get(artifact.contentHash);
1773
+ if (stagedPaths && !hasSealedContent(cacheDir, artifact.contentHash, manifest.bytes)) {
1774
+ stagedArtifacts += 1;
1775
+ targets.set(artifact.contentHash, stagedSet.target(artifact, manifest, stagedPaths));
1776
+ }
1777
+ else {
1778
+ targets.set(artifact.contentHash, downloads.target(artifact, manifest));
1779
+ }
1780
+ });
1759
1781
  return manifest;
1760
1782
  },
1761
- hasChunk: async (artifact, _manifest, index) => target(artifact).hasChunk(index),
1783
+ hasChunk: async (artifact, _manifest, index) => legs.time("reuseMs", () => target(artifact).hasChunk(index)),
1762
1784
  getChunk: async (_artifact, manifest, index) => {
1763
1785
  const chunk = manifest.chunks[index];
1764
1786
  if (!chunk)
1765
1787
  throw new Error(`content manifest has no chunk ${index}`);
1766
1788
  return fetchObject({ kind: "block", sha256: chunk.sha256 });
1767
1789
  },
1768
- putChunk: async (artifact, _manifest, index, bytes) => {
1769
- const writeStarted = performance.now();
1770
- await target(artifact).putChunk(index, bytes);
1771
- cacheWriteMs += performance.now() - writeStarted;
1772
- },
1790
+ putChunk: async (artifact, _manifest, index, bytes) => legs.time("writeMs", () => target(artifact).putChunk(index, bytes)),
1773
1791
  sealArtifact: async (artifact) => {
1774
- const sealStarted = performance.now();
1775
- const sealed = await target(artifact).seal();
1776
- cacheWriteMs += performance.now() - sealStarted;
1792
+ const sealed = await legs.time("sealMs", () => target(artifact).seal());
1777
1793
  return { local: sealed, bytes: sealed.bytes, contentHash: sealed.contentHash };
1778
1794
  },
1779
1795
  }, {
@@ -1803,12 +1819,17 @@ export class UserGroundHost {
1803
1819
  reusedChunks,
1804
1820
  // `requests` is logical object reads; the plane deltas below are
1805
1821
  // 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.
1822
+ // wire, refs round-trips, decrypt CPU. The legs place local time:
1823
+ // plan (manifest cache + routing), fetch (storage waits, whose
1824
+ // decrypt share the plane delta names), hash (chunk verification),
1825
+ // reuse (retained-chunk re-proof), write (chunk placement), seal
1826
+ // (whole-file proof + durability), and idle — wall time with
1827
+ // nothing in flight. The staged rail decomposes its own write and
1828
+ // seal internals below; `stagedArtifacts` counts its placements.
1809
1829
  requests,
1810
1830
  ...physicalPlaneDelta(planeBefore, this.direct.metrics()),
1811
- cacheWriteMs: Math.round(cacheWriteMs),
1831
+ ...legs.report(),
1832
+ ...(stagedSet ? stagedSet.report() : {}),
1812
1833
  ...(stagedArtifacts > 0 ? { stagedArtifacts } : {}),
1813
1834
  },
1814
1835
  });
@@ -1826,6 +1847,7 @@ export class UserGroundHost {
1826
1847
  artifacts: unique.size,
1827
1848
  ...(sole ? { kind: sole.kind } : {}),
1828
1849
  requests,
1850
+ ...legs.report(),
1829
1851
  },
1830
1852
  });
1831
1853
  throw error;
@@ -2425,7 +2447,8 @@ export class UserGroundHost {
2425
2447
  if (pages.length === 0)
2426
2448
  return;
2427
2449
  const started = performance.now();
2428
- const sealed = await this.sealedContentHashes(resourceId, pages.map((page) => page.sha256));
2450
+ const legs = new TransferLegs();
2451
+ const sealed = await legs.time("planMs", () => this.sealedContentHashes(resourceId, pages.map((page) => page.sha256)));
2429
2452
  const unsealed = pages
2430
2453
  .filter((page) => !sealed.has(page.sha256))
2431
2454
  .map((page) => ({ contentHash: page.sha256, bytes: page.bytes.byteLength, page }));
@@ -2439,7 +2462,7 @@ export class UserGroundHost {
2439
2462
  content_hash: item.contentHash,
2440
2463
  bytes: item.bytes,
2441
2464
  chunks: [{ sha256: item.contentHash, bytes: item.bytes }],
2442
- })), Buffer.concat(segment.items.map((item) => item.page.bytes)));
2465
+ })), Buffer.concat(segment.items.map((item) => item.page.bytes)), legs);
2443
2466
  uploadedBytes += wireBytes;
2444
2467
  return stored;
2445
2468
  },
@@ -2455,6 +2478,7 @@ export class UserGroundHost {
2455
2478
  pages: pages.length,
2456
2479
  uploadedPages: unsealed.length,
2457
2480
  uploadedBytes,
2481
+ ...legs.report(),
2458
2482
  },
2459
2483
  });
2460
2484
  }