@amalgm/shell 0.1.134 → 0.1.135

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.
@@ -38,8 +38,8 @@ const CARGO_SEGMENT_LOAD_CONCURRENCY = 8;
38
38
  /** Storage-object lanes for one download set. Tiny objects use the wide
39
39
  * request window, while the separate byte budget narrows large chunks so
40
40
  * request concurrency can never silently become an unbounded memory claim. */
41
- /** Admission is sized to the refs batch law so one same-turn wave coalesces
42
- * into one refs frame and the span fetcher merges each pack's members into a
41
+ /** Admission is sized to the refs batch law so one planned wave resolves in
42
+ * one refs frame and the span planner merges each pack's members into a
43
43
  * few broad ranged reads. Memory is bounded by the byte laws below, never by
44
44
  * this count. */
45
45
  const DIRECT_OBJECT_DOWNLOAD_CONCURRENCY = CONTENT_REFS_BATCH_OBJECTS;
@@ -685,7 +685,14 @@ export class UserGroundHost {
685
685
  const fusionResourceId = this.cloudState.resourceId;
686
686
  const fusionLegs = new TransferLegs();
687
687
  const fusion = new RegisterUploadFusion({
688
- sealedContentHashes: (hashes) => this.sealedContentHashes(fusionResourceId, hashes),
688
+ // Fusion ships whole small artifacts into packs; skipping one
689
+ // requires it to be both sealed and packed, so sealed-but-
690
+ // standalone smalls re-ship here and placement converges on
691
+ // packs without a separate migration.
692
+ sealedContentHashes: async (hashes) => {
693
+ const inventory = await this.sealInventory(fusionResourceId, hashes);
694
+ return new Set([...inventory.sealed].filter((hash) => inventory.packed.has(hash)));
695
+ },
689
696
  sendCargo: async (artifacts, payload) => {
690
697
  const { sealed } = await this.putCargo(fusionResourceId, artifacts, payload, fusionLegs);
691
698
  return sealed;
@@ -923,10 +930,11 @@ export class UserGroundHost {
923
930
  throw new Error("entity Record rail already started");
924
931
  await this.wire.open();
925
932
  // One floor, no per-feature gates: this shell and its gateway release
926
- // together, and the gateway ships first. 15 = one-read Register capture:
927
- // large-file blocks become durable while capture owns their bytes, then
928
- // the manifest seals last. An older gateway cannot accept that rail.
929
- if (this.wire.protocolVersion < 15) {
933
+ // together, and the gateway ships first. 16 = placement answers on the
934
+ // seal inventory; an older gateway cannot say which sealed artifacts are
935
+ // packed, and this shell plans pack-span downloads and repacks against
936
+ // exactly that answer.
937
+ if (this.wire.protocolVersion < 16) {
930
938
  throw new Error("gateway speaks an older wire protocol than this shell");
931
939
  }
932
940
  this.adoptCloudDeliveryBaseline(identity, this.cloudState.deliveryWatermark);
@@ -1538,11 +1546,16 @@ export class UserGroundHost {
1538
1546
  // The authority's seal inventory is the only permission to skip an
1539
1547
  // upload — asked before any source is touched, so sealed artifacts cost
1540
1548
  // zero local reads and a retried publication whose upload already sealed
1541
- // completely never needs its captured ground to still exist.
1542
- const alreadySealed = await legs.time("planMs", () => this.sealedContentHashes(resourceId, [...byHash.keys()]));
1549
+ // completely never needs its captured ground to still exist. Placement
1550
+ // rides the same answer: a sealed-but-unpacked small artifact is
1551
+ // correctness-complete but stored inefficiently, so this guarantor also
1552
+ // re-ships it into a pack when its source is still at hand (axiom 44).
1553
+ const inventory = await legs.time("planMs", () => this.sealInventory(resourceId, [...byHash.keys()]));
1543
1554
  const unsealed = [...byHash.values()]
1544
- .filter(({ artifact }) => !alreadySealed.has(artifact.contentHash));
1545
- const rows = legs.timeSync("planMs", () => readGroundRowsByUuid(this.databasePath(identity), unsealed.map(({ uuid }) => uuid)));
1555
+ .filter(({ artifact }) => !inventory.sealed.has(artifact.contentHash));
1556
+ const unpacked = [...byHash.values()].filter(({ artifact }) => inventory.sealed.has(artifact.contentHash)
1557
+ && !inventory.packed.has(artifact.contentHash));
1558
+ const rows = legs.timeSync("planMs", () => readGroundRowsByUuid(this.databasePath(identity), [...unsealed, ...unpacked].map(({ uuid }) => uuid)));
1546
1559
  // Each artifact uploads from the strongest evidence available: a cache
1547
1560
  // copy when another journey pinned one, otherwise the ground entity
1548
1561
  // itself. Register stages nothing, so the ground path is the common
@@ -1589,6 +1602,42 @@ export class UserGroundHost {
1589
1602
  bytes,
1590
1603
  };
1591
1604
  }));
1605
+ // Repack candidates are sealed already, so their sources are optional:
1606
+ // anything missing, oversized, or unreadable skips silently — a frozen
1607
+ // workspace simply keeps standalone placement until its next actual
1608
+ // publication. What readers see never changes either way.
1609
+ const repackSources = legs.timeSync("planMs", () => unpacked.flatMap(({ artifact, uuid }) => {
1610
+ const cached = contentCacheFile(cache, artifact.contentHash);
1611
+ const fromCache = existsSync(cached);
1612
+ const row = rows.get(uuid);
1613
+ if (!fromCache && (artifact.kind === "git" || !row))
1614
+ return [];
1615
+ const file = fromCache ? cached : row.absolutePath;
1616
+ let linkTarget;
1617
+ let bytes;
1618
+ try {
1619
+ if (!fromCache && artifact.kind === "link") {
1620
+ linkTarget = Buffer.from(readlinkSync(file), "utf8");
1621
+ bytes = linkTarget.byteLength;
1622
+ }
1623
+ else {
1624
+ bytes = statSync(file).size;
1625
+ }
1626
+ }
1627
+ catch {
1628
+ return [];
1629
+ }
1630
+ if (bytes > CHUNK_BYTES)
1631
+ return [];
1632
+ return [{
1633
+ artifact,
1634
+ file,
1635
+ fromCache,
1636
+ ...(linkTarget ? { linkTarget } : {}),
1637
+ bytes,
1638
+ repack: true,
1639
+ }];
1640
+ }));
1592
1641
  const stale = (source, error) => {
1593
1642
  if (source.fromCache) {
1594
1643
  // A cache copy that no longer proves its hash is torn evidence:
@@ -1608,8 +1657,11 @@ export class UserGroundHost {
1608
1657
  const small = sources.filter((source) => source.bytes <= CHUNK_BYTES);
1609
1658
  const large = sources.filter((source) => source.bytes > CHUNK_BYTES);
1610
1659
  let cargoSegments = 0;
1660
+ let repackedArtifacts = 0;
1661
+ let repackedBytes = 0;
1611
1662
  const sendCargoSegment = async (segment) => {
1612
- const loaded = new Array(segment.items.length);
1663
+ const loaded = new Array(segment.items.length).fill(null);
1664
+ const dropped = [];
1613
1665
  await boundedForEach(segment.items.map((item, index) => ({ source: item.source, index })), CARGO_SEGMENT_LOAD_CONCURRENCY, async ({ source, index }) => {
1614
1666
  try {
1615
1667
  const bytes = source.linkTarget ?? await legs.time("loadMs", () => readFile(source.file));
@@ -1620,21 +1672,39 @@ export class UserGroundHost {
1620
1672
  };
1621
1673
  }
1622
1674
  catch (error) {
1675
+ // A repack source that churned or vanished under this read is
1676
+ // dropped, never stale: the artifact stays sealed standalone,
1677
+ // and it is answered as sealed because it truthfully is.
1678
+ if (source.repack) {
1679
+ dropped.push(source.artifact.contentHash);
1680
+ return;
1681
+ }
1623
1682
  if (sourceMismatch(error))
1624
1683
  stale(source, error);
1625
1684
  throw error;
1626
1685
  }
1627
1686
  });
1628
- const { sealed, wireBytes } = await this.putCargo(resourceId, loaded.map((item, index) => ({
1629
- content_hash: segment.items[index].contentHash,
1630
- bytes: item.manifest.bytes,
1631
- chunks: item.manifest.chunks.map((chunk) => ({ sha256: chunk.sha256, bytes: chunk.bytes })),
1632
- })), Buffer.concat(loaded.map((item) => item.bytes)), legs);
1633
- for (const item of loaded)
1634
- uploadedChunks += item.manifest.chunks.length;
1687
+ const shipping = segment.items.flatMap((item, index) => {
1688
+ const load = loaded[index];
1689
+ return load === null ? [] : [{ item, load }];
1690
+ });
1691
+ if (shipping.length === 0)
1692
+ return dropped;
1693
+ const { sealed, wireBytes } = await this.putCargo(resourceId, shipping.map(({ item, load }) => ({
1694
+ content_hash: item.contentHash,
1695
+ bytes: load.manifest.bytes,
1696
+ chunks: load.manifest.chunks.map((chunk) => ({ sha256: chunk.sha256, bytes: chunk.bytes })),
1697
+ })), Buffer.concat(shipping.map(({ load }) => load.bytes)), legs);
1698
+ for (const { item, load } of shipping) {
1699
+ uploadedChunks += load.manifest.chunks.length;
1700
+ if (item.source.repack) {
1701
+ repackedArtifacts += 1;
1702
+ repackedBytes += load.manifest.bytes;
1703
+ }
1704
+ }
1635
1705
  uploadedBytes += wireBytes;
1636
1706
  cargoSegments += 1;
1637
- return sealed;
1707
+ return [...sealed, ...dropped];
1638
1708
  };
1639
1709
  const transfer = async (source) => {
1640
1710
  const { artifact, file, manifest: knownManifest, bytes: fileBytes } = source;
@@ -1701,7 +1771,7 @@ export class UserGroundHost {
1701
1771
  }
1702
1772
  };
1703
1773
  try {
1704
- await streamCargo(small.map((source) => ({
1774
+ await streamCargo([...small, ...repackSources].map((source) => ({
1705
1775
  contentHash: source.artifact.contentHash,
1706
1776
  bytes: source.bytes,
1707
1777
  source,
@@ -1728,6 +1798,12 @@ export class UserGroundHost {
1728
1798
  cargoBytes: small.reduce((total, source) => total + source.bytes, 0),
1729
1799
  largeArtifacts: large.length,
1730
1800
  largeBytes: large.reduce((total, source) => total + source.bytes, 0),
1801
+ // Placement work: sealed-but-standalone smalls re-shipped into
1802
+ // packs by this guarantor. Candidates counts what the inventory
1803
+ // named; repacked counts what actually shipped this pass.
1804
+ repackCandidates: unpacked.length,
1805
+ repackedArtifacts,
1806
+ repackedBytes,
1731
1807
  uploadedBytes,
1732
1808
  loadedBytes,
1733
1809
  uploadedChunks,
@@ -1757,10 +1833,11 @@ export class UserGroundHost {
1757
1833
  }
1758
1834
  }
1759
1835
  /** Download a set of immutable artifacts straight from content storage.
1760
- * The set is the primitive: every storage object the set still needs —
1761
- * manifests and blocks alike — shares one wide lane bound, so no artifact
1762
- * serializes behind another's plan or seal. Each object is still its own
1763
- * storage request. A lone artifact is a set of one. */
1836
+ * The set is the primitive and the plan is physical: the engine resolves
1837
+ * the whole want-list in two planned waves — manifests grouped by the
1838
+ * pack that holds them, then blocks the same way — so all members of one
1839
+ * pack collapse into a few broad ranged reads while standalone objects
1840
+ * stay one read each. A lone artifact is a set of one. */
1764
1841
  async downloadContentSet(resourceId, cacheDir, wants, retryAttempts = 10, trace, staged) {
1765
1842
  const unique = new Map(wants.map((want) => [want.artifact.contentHash, want]));
1766
1843
  const sole = unique.size === 1 ? [...unique.values()][0]?.artifact : undefined;
@@ -1801,56 +1878,75 @@ export class UserGroundHost {
1801
1878
  throw new Error("download cache has no content manifest");
1802
1879
  return found;
1803
1880
  };
1804
- // Every storage object — the manifest and each block — is fetched
1805
- // straight from content storage over a presigned URL and opened locally;
1806
- // packed objects arrive as merged byte ranges of their immutable pack.
1807
- // The wire carries only the key handoff and refs requests. A retry asks
1808
- // for a fresh ref every attempt.
1809
- const fetchObject = async (object) => {
1810
- requests += 1;
1811
- const bytes = await legs.time("fetchMs", () => retryContent(() => this.direct.fetchObject(resourceId, object), true, retryAttempts));
1812
- downloadedBytes += bytes.byteLength;
1813
- return bytes;
1881
+ // An Add stages ordinary files directly at their hidden final
1882
+ // destination — except content this machine already holds sealed,
1883
+ // which stays on the cache rail so a warm re-Add remains a local
1884
+ // clone instead of a fresh download.
1885
+ const route = (artifact, manifest) => {
1886
+ const stagedPaths = stagedSet && staged?.destinations.get(artifact.contentHash);
1887
+ if (stagedPaths && !hasSealedContent(cacheDir, artifact.contentHash, manifest.bytes)) {
1888
+ stagedArtifacts += 1;
1889
+ targets.set(artifact.contentHash, stagedSet.target(artifact, manifest, stagedPaths));
1890
+ }
1891
+ else {
1892
+ targets.set(artifact.contentHash, downloads.target(artifact, manifest));
1893
+ }
1814
1894
  };
1815
1895
  try {
1816
- const receipts = await downloadAllArtifacts([...unique.values()].map((want) => want.artifact), {
1896
+ // Every storage object — manifests and blocks alike — is fetched
1897
+ // straight from content storage over a presigned URL and opened
1898
+ // locally; the engine plans whole waves against these primitives, so
1899
+ // the wire carries only the key handoff and batched refs requests. A
1900
+ // retried read refreshes its URL but never its geometry: placement is
1901
+ // immutable, and a moved answer fails loudly instead of retrying.
1902
+ const { receipts, shape } = await downloadAllArtifacts([...unique.values()].map((want) => want.artifact), {
1817
1903
  sha256Hex: timedSha256Hex,
1818
- getManifest: async (artifact) => {
1904
+ localManifest: (artifact) => legs.timeSync("planMs", () => {
1819
1905
  // The record's plan or this machine's own manifest cache already
1820
1906
  // proves chunk identity; downloaded bytes are still hash-verified,
1821
1907
  // so a stale local manifest can only fail loudly, never corrupt.
1822
- const local = legs.timeSync("planMs", () => unique.get(artifact.contentHash)?.manifest
1823
- ?? readCachedManifest(cacheDir, artifact.contentHash));
1824
- const manifest = local ?? await (async () => {
1825
- const bytes = await fetchObject({ kind: "manifest", sha256: artifact.contentHash });
1826
- const checked = checkContentManifest(JSON.parse(bytes.toString("utf8")), artifact.contentHash);
1827
- if (!checked.ok)
1828
- throw new Error(checked.error);
1829
- return checked.value;
1830
- })();
1831
- // An Add stages ordinary files directly at their hidden final
1832
- // destination — except content this machine already holds sealed,
1833
- // which stays on the cache rail so a warm re-Add remains a local
1834
- // clone instead of a fresh download.
1835
- legs.timeSync("planMs", () => {
1836
- const stagedPaths = stagedSet && staged?.destinations.get(artifact.contentHash);
1837
- if (stagedPaths && !hasSealedContent(cacheDir, artifact.contentHash, manifest.bytes)) {
1838
- stagedArtifacts += 1;
1839
- targets.set(artifact.contentHash, stagedSet.target(artifact, manifest, stagedPaths));
1840
- }
1841
- else {
1842
- targets.set(artifact.contentHash, downloads.target(artifact, manifest));
1908
+ const local = unique.get(artifact.contentHash)?.manifest
1909
+ ?? readCachedManifest(cacheDir, artifact.contentHash);
1910
+ if (local)
1911
+ route(artifact, local);
1912
+ return local ?? null;
1913
+ }),
1914
+ requestRefs: async (objects) => legs.time("fetchMs", () => retryContent(() => this.direct.requestRefs(resourceId, objects), true, retryAttempts)),
1915
+ fetchSpan: async (url, span, refresh) => legs.time("fetchMs", async () => {
1916
+ let attempt = 0;
1917
+ return retryContent(async () => {
1918
+ let signed = url;
1919
+ if (attempt > 0) {
1920
+ try {
1921
+ signed = await refresh();
1922
+ }
1923
+ catch (error) {
1924
+ // A moved placement (or a refused re-resolution) is a
1925
+ // planning fact, not a transient fetch fault — surface it
1926
+ // without burning the remaining attempts.
1927
+ throw new WireRequestError({
1928
+ type: "shared.error",
1929
+ code: "entity_content_moved",
1930
+ message: error instanceof Error ? error.message : String(error),
1931
+ status: 409,
1932
+ retryable: false,
1933
+ });
1934
+ }
1843
1935
  }
1844
- });
1845
- return manifest;
1936
+ attempt += 1;
1937
+ return this.direct.fetchSpan(signed, span);
1938
+ }, true, retryAttempts);
1939
+ }),
1940
+ openEnvelope: async (_object, sealed) => {
1941
+ requests += 1;
1942
+ const plain = await legs.time("fetchMs", () => this.direct.openEnvelope(resourceId, sealed));
1943
+ downloadedBytes += plain.byteLength;
1944
+ return plain;
1846
1945
  },
1847
- hasChunk: async (artifact, _manifest, index) => legs.time("reuseMs", () => target(artifact).hasChunk(index)),
1848
- getChunk: async (_artifact, manifest, index) => {
1849
- const chunk = manifest.chunks[index];
1850
- if (!chunk)
1851
- throw new Error(`content manifest has no chunk ${index}`);
1852
- return fetchObject({ kind: "block", sha256: chunk.sha256 });
1946
+ manifestProven: async (artifact, manifest) => {
1947
+ legs.timeSync("planMs", () => route(artifact, manifest));
1853
1948
  },
1949
+ hasChunk: async (artifact, _manifest, index) => legs.time("reuseMs", () => target(artifact).hasChunk(index)),
1854
1950
  putChunk: async (artifact, _manifest, index, bytes) => legs.time("writeMs", () => target(artifact).putChunk(index, bytes)),
1855
1951
  sealArtifact: async (artifact) => {
1856
1952
  const sealed = await legs.time("sealMs", () => target(artifact).seal());
@@ -1891,6 +1987,10 @@ export class UserGroundHost {
1891
1987
  // nothing in flight. The staged rail decomposes its own write and
1892
1988
  // seal internals below; `stagedArtifacts` counts its placements.
1893
1989
  requests,
1990
+ // The engine's own geometry: how manifests were known (local vs
1991
+ // fetched, packed vs standalone) and how many planned pack spans
1992
+ // carried the packed objects.
1993
+ ...shape,
1894
1994
  ...physicalPlaneDelta(planeBefore, this.direct.metrics()),
1895
1995
  ...legs.report(),
1896
1996
  ...(stagedSet ? stagedSet.report() : {}),
@@ -2471,27 +2571,35 @@ export class UserGroundHost {
2471
2571
  });
2472
2572
  }
2473
2573
  /** Which of these heads does the authority hold sealed under the current
2474
- * content contract? Batched so a whole workspace costs a handful of
2475
- * frames; the answer is the only permission to skip an upload. */
2476
- async sealedContentHashes(resourceId, hashes) {
2574
+ * content contract — and which of the sealed ones are packed, their whole
2575
+ * closure inside immutable packs? Batched so a whole workspace costs a
2576
+ * handful of frames. `sealed` is the only permission to skip an upload
2577
+ * for correctness; `packed` is placement knowledge only. The packed
2578
+ * answer is a protocol 16 capability and is required: a missing field
2579
+ * means an older gateway, and this machine refuses to guess placement. */
2580
+ async sealInventory(resourceId, hashes) {
2477
2581
  const batches = [];
2478
2582
  for (let start = 0; start < hashes.length; start += CONTENT_REFS_BATCH_OBJECTS) {
2479
2583
  batches.push(hashes.slice(start, start + CONTENT_REFS_BATCH_OBJECTS));
2480
2584
  }
2481
2585
  const sealed = new Set();
2586
+ const packed = new Set();
2482
2587
  const answers = await Promise.all(batches.map((batch) => retryContent(() => this.wire.request({
2483
2588
  type: "private.entity-content.sealed",
2484
2589
  resource_id: resourceId,
2485
2590
  content_hashes: batch,
2486
2591
  }, ["private.entity-content.sealed-result"]))));
2487
2592
  for (const frame of answers) {
2488
- if (!Array.isArray(frame.sealed)) {
2489
- throw new Error("content authority answered an invalid seal inventory");
2593
+ if (!Array.isArray(frame.sealed) || !Array.isArray(frame.packed)) {
2594
+ throw new Error("content authority answered a seal inventory without "
2595
+ + "placement; protocol 16 requires the gateway to deploy before this machine");
2490
2596
  }
2491
2597
  for (const hash of frame.sealed)
2492
2598
  sealed.add(String(hash));
2599
+ for (const hash of frame.packed)
2600
+ packed.add(String(hash));
2493
2601
  }
2494
- return sealed;
2602
+ return { sealed, packed };
2495
2603
  }
2496
2604
  async publishPendingSnapshots(identity, trace, manifestHints) {
2497
2605
  for (const pending of readSnapshotPublications(this.databasePath(identity))) {
@@ -2512,7 +2620,7 @@ export class UserGroundHost {
2512
2620
  return;
2513
2621
  const started = performance.now();
2514
2622
  const legs = new TransferLegs();
2515
- const sealed = await legs.time("planMs", () => this.sealedContentHashes(resourceId, pages.map((page) => page.sha256)));
2623
+ const { sealed } = await legs.time("planMs", () => this.sealInventory(resourceId, pages.map((page) => page.sha256)));
2516
2624
  const unsealed = pages
2517
2625
  .filter((page) => !sealed.has(page.sha256))
2518
2626
  .map((page) => ({ contentHash: page.sha256, bytes: page.bytes.byteLength, page }));