@amalgm/shell 0.1.126 → 0.1.128

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.
@@ -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";
@@ -383,7 +384,7 @@ export class UserGroundHost {
383
384
  destinationParent: input.destinationParent,
384
385
  cloudHead: input.cloudHead,
385
386
  deliveryWatermark: input.deliveryWatermark,
386
- 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),
387
388
  adoptMaterialization: (rootUUID) => {
388
389
  const settled = this.adoptCloudDeliveryBaseline(identity, input.deliveryWatermark, rootUUID);
389
390
  this.applyRail?.recordsAvailable();
@@ -492,8 +493,9 @@ export class UserGroundHost {
492
493
  if (stagedAcrossBlindInterval && hasRootRows(database, intent.resourceId, intent.workspaceId)) {
493
494
  // Rows prove that staging was verified before the crash, not that its
494
495
  // bytes remained unchanged while no watcher existed. Return this
495
- // private install to the intent-only stage and reproduce it from the
496
- // 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.
497
499
  deleteRootRows(database, intent.resourceId, intent.workspaceId);
498
500
  }
499
501
  await installCloudWorkspace({
@@ -510,7 +512,7 @@ export class UserGroundHost {
510
512
  destinationParent: dirname(intent.destinationPath),
511
513
  cloudHead: intent.cloudHead,
512
514
  deliveryWatermark: intent.deliveryWatermark,
513
- 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),
514
516
  adoptMaterialization: (rootUUID) => {
515
517
  this.adoptCloudDeliveryBaseline(identity, intent.deliveryWatermark, rootUUID);
516
518
  this.applyRail?.recordsAvailable();
@@ -1676,7 +1678,7 @@ export class UserGroundHost {
1676
1678
  * manifests and blocks alike — shares one wide lane bound, so no artifact
1677
1679
  * serializes behind another's plan or seal. Each object is still its own
1678
1680
  * storage request. A lone artifact is a set of one. */
1679
- async downloadContentSet(resourceId, cacheDir, wants, retryAttempts = 10, trace) {
1681
+ async downloadContentSet(resourceId, cacheDir, wants, retryAttempts = 10, trace, staged) {
1680
1682
  const unique = new Map(wants.map((want) => [want.artifact.contentHash, want]));
1681
1683
  const sole = unique.size === 1 ? [...unique.values()][0]?.artifact : undefined;
1682
1684
  const started = performance.now();
@@ -1687,6 +1689,7 @@ export class UserGroundHost {
1687
1689
  let reusedChunks = 0;
1688
1690
  let requests = 0;
1689
1691
  let cacheWriteMs = 0;
1692
+ let stagedArtifacts = 0;
1690
1693
  const entityId = trace?.entityId ?? sole?.entityId;
1691
1694
  const identifiers = {
1692
1695
  ...(trace?.workspaceId ? { workspaceId: trace.workspaceId } : {}),
@@ -1706,6 +1709,7 @@ export class UserGroundHost {
1706
1709
  },
1707
1710
  });
1708
1711
  const downloads = new ContentCacheDownloadSet(cacheDir);
1712
+ const stagedSet = staged ? new StagedContentSet(staged.evidenceDir) : null;
1709
1713
  const targets = new Map();
1710
1714
  const target = (artifact) => {
1711
1715
  const found = targets.get(artifact.contentHash);
@@ -1740,7 +1744,18 @@ export class UserGroundHost {
1740
1744
  throw new Error(checked.error);
1741
1745
  return checked.value;
1742
1746
  })();
1743
- 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
+ }
1744
1759
  return manifest;
1745
1760
  },
1746
1761
  hasChunk: async (artifact, _manifest, index) => target(artifact).hasChunk(index),
@@ -1788,10 +1803,13 @@ export class UserGroundHost {
1788
1803
  reusedChunks,
1789
1804
  // `requests` is logical object reads; the plane deltas below are
1790
1805
  // the physical facts — actual storage GETs, sealed bytes on the
1791
- // wire, refs round-trips, decrypt and cache-write CPU.
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.
1792
1809
  requests,
1793
1810
  ...physicalPlaneDelta(planeBefore, this.direct.metrics()),
1794
1811
  cacheWriteMs: Math.round(cacheWriteMs),
1812
+ ...(stagedArtifacts > 0 ? { stagedArtifacts } : {}),
1795
1813
  },
1796
1814
  });
1797
1815
  return content;
@@ -1815,6 +1833,7 @@ export class UserGroundHost {
1815
1833
  finally {
1816
1834
  await Promise.all([...targets.values()].map((open) => open.close()));
1817
1835
  downloads.close();
1836
+ stagedSet?.close();
1818
1837
  }
1819
1838
  }
1820
1839
  async downloadContent(resourceId, cacheDir, artifact, retryAttempts = 10, trace, knownManifest) {
@@ -5438,9 +5457,17 @@ function installCloudGround(input) {
5438
5457
  })();
5439
5458
  }
5440
5459
  async function materializeRecords(input) {
5441
- const { records, rootPath, cacheDir, bindingDir, readContent } = input;
5460
+ const { records, rootPath, cacheDir, bindingDir, staged, readContent } = input;
5442
5461
  const repositories = [];
5443
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
+ };
5444
5471
  // One download set covers everything the wire owes this add up front:
5445
5472
  // every ordinary file artifact and every repository transport head, side
5446
5473
  // by side under one wide object bound. Only transport parents — links a
@@ -5448,6 +5475,7 @@ async function materializeRecords(input) {
5448
5475
  // round is again one set across every chain. Disk application below keeps
5449
5476
  // its ordered, validated walk.
5450
5477
  const wanted = new Map();
5478
+ const stagedDestinations = staged ? new Map() : null;
5451
5479
  const repositoryHeads = [];
5452
5480
  for (const record of records) {
5453
5481
  if (record.type === "repo.git") {
@@ -5462,11 +5490,30 @@ async function materializeRecords(input) {
5462
5490
  }
5463
5491
  else if (["file.text", "file.binary", "link"].includes(record.type)) {
5464
5492
  const artifact = artifactForRecord(record);
5465
- if (artifact)
5466
- 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
+ }
5467
5506
  }
5468
5507
  }
5469
- 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);
5470
5517
  const repositoryChains = new Map();
5471
5518
  const walking = repositoryHeads.map(({ record, artifact }) => {
5472
5519
  const local = fileContent.get(artifact.contentHash);
@@ -5520,7 +5567,6 @@ async function materializeRecords(input) {
5520
5567
  repositories.push(repository);
5521
5568
  repositoriesByUuid.set(record.uuid, repository);
5522
5569
  };
5523
- const paths = portablePaths(records, input.boundaryUUID ? new Set([input.boundaryUUID]) : new Set());
5524
5570
  const recordsByUuid = new Map(records.map((record) => [record.uuid, record]));
5525
5571
  const repositoryOwner = (record) => {
5526
5572
  let parentUUID = record.parentUUID;
@@ -5558,29 +5604,59 @@ async function materializeRecords(input) {
5558
5604
  return leftPath.split("/").length - rightPath.split("/").length
5559
5605
  || (leftPath < rightPath ? -1 : leftPath > rightPath ? 1 : 0);
5560
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
+ };
5561
5623
  for (const record of ordered) {
5562
5624
  const relativePath = paths.get(record.uuid);
5563
- const destination = relativePath ? join(rootPath, ...relativePath.split("/")) : rootPath;
5564
- if (!pathWithin(rootPath, destination)) {
5565
- throw new Error(`cloud entity path escapes its workspace: ${relativePath}`);
5566
- }
5625
+ const destination = destinationOf(relativePath);
5626
+ claim(relativePath);
5567
5627
  if (relativePath === "") {
5568
- if (record.type === "repo.git")
5628
+ if (record.type === "repo.git") {
5629
+ if (staged && pathExists(destination)) {
5630
+ rmSync(destination, { recursive: true, force: true });
5631
+ }
5569
5632
  await materializeRepository(record, destination);
5570
- else
5633
+ }
5634
+ else {
5571
5635
  ensureUserDir(destination);
5636
+ }
5572
5637
  }
5573
5638
  else if (record.type === "folder") {
5574
- if (pathExists(destination)) {
5639
+ if (!pathExists(destination)) {
5640
+ ensureUserDir(destination);
5641
+ }
5642
+ else if (!staged || repositoryOwner(record)) {
5575
5643
  assertRepositoryBoundaryPath(record, relativePath, destination);
5576
5644
  }
5577
5645
  else {
5578
- ensureUserDir(destination);
5646
+ const stats = lstatSync(destination);
5647
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
5648
+ throw new Error(`cloud materialization conflicts with ${relativePath}`);
5649
+ }
5579
5650
  }
5580
5651
  }
5581
5652
  else if (record.type === "repo.git") {
5582
5653
  if (pathExists(destination)) {
5583
- 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
+ }
5584
5660
  }
5585
5661
  await materializeRepository(record, destination);
5586
5662
  }
@@ -5588,8 +5664,11 @@ async function materializeRecords(input) {
5588
5664
  if (!record.payloadVersion || !UUID.test(record.payloadVersion)) {
5589
5665
  throw new Error(`cloud reference ${record.uuid} has no workspace identity`);
5590
5666
  }
5591
- if (pathExists(destination))
5667
+ if (staged)
5668
+ rmSync(destination, { force: true });
5669
+ else if (pathExists(destination)) {
5592
5670
  throw new Error(`cloud materialization conflicts with ${relativePath}`);
5671
+ }
5593
5672
  ensureUserDir(dirname(destination));
5594
5673
  const binding = join(bindingDir, record.payloadVersion.toLowerCase());
5595
5674
  symlinkSync(relative(dirname(destination), binding), destination);
@@ -5597,21 +5676,41 @@ async function materializeRecords(input) {
5597
5676
  else if (["file.text", "file.binary", "link"].includes(record.type)) {
5598
5677
  if (!record.payloadVersion)
5599
5678
  throw new Error(`cloud leaf ${record.uuid} has no content head`);
5600
- if (pathExists(destination))
5601
- throw new Error(`cloud materialization conflicts with ${relativePath}`);
5602
5679
  const artifact = artifactForRecord(record);
5603
5680
  if (!artifact)
5604
5681
  throw new Error(`cloud leaf ${record.uuid} has no transfer artifact`);
5605
5682
  const local = fileContent.get(artifact.contentHash);
5606
5683
  if (!local)
5607
5684
  throw new Error(`cloud leaf ${record.uuid} content was not downloaded`);
5608
- ensureUserDir(dirname(destination));
5609
- 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));
5610
5692
  symlinkSync(readFileSync(local.file, "utf8"), destination);
5611
- // Mode is not portable state: a fresh user file gets 0644, the same
5612
- // default Apply gives a fresh target, never the cache's private 0600.
5613
- 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.
5614
5712
  atomicCopy(local.file, destination, 0o644);
5713
+ }
5615
5714
  }
5616
5715
  else {
5617
5716
  throw new Error(`materialization for ${record.type} requires its dedicated adapter`);
@@ -5756,6 +5855,7 @@ async function installCloudWorkspace(input) {
5756
5855
  throw new Error(`local workspace ${workspace.uuid} has unresolved placement`);
5757
5856
  }
5758
5857
  renameSync(pending.stagingPath, pending.destinationPath);
5858
+ clearAddStagingEvidence(database, workspace.uuid);
5759
5859
  revealed = true;
5760
5860
  }
5761
5861
  else if (pending && pathExists(pending.stagingPath)) {
@@ -5763,6 +5863,7 @@ async function installCloudWorkspace(input) {
5763
5863
  }
5764
5864
  if (pending && pathExists(root.absolutePath)) {
5765
5865
  refreshRevealedRootEvidence(database, workspace.uuid, root.absolutePath);
5866
+ clearAddStagingEvidence(database, workspace.uuid);
5766
5867
  }
5767
5868
  if (revealed)
5768
5869
  await onStage?.("ground-revealed");
@@ -5864,9 +5965,9 @@ async function installCloudWorkspace(input) {
5864
5965
  if (pathExists(binding)) {
5865
5966
  throw new Error(`workspace ${workspace.uuid} has a binding without committed entity rows`);
5866
5967
  }
5867
- if (pathExists(intent.stagingPath)) {
5868
- rmSync(intent.stagingPath, { recursive: true, force: true });
5869
- }
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.
5870
5971
  const selection = workspaceAddSelection(intent);
5871
5972
  const materialized = await materializeRecords({
5872
5973
  records: intent.records,
@@ -5874,6 +5975,7 @@ async function installCloudWorkspace(input) {
5874
5975
  boundaryUUID: intent.workspaceId,
5875
5976
  cacheDir,
5876
5977
  bindingDir,
5978
+ staged: { evidenceDir: addStagingEvidenceDir(database, intent.workspaceId) },
5877
5979
  readContent,
5878
5980
  });
5879
5981
  const rows = projectMaterializedGraph(materialized.rows, materialized.repositories.map((repository) => ({
@@ -5890,6 +5992,7 @@ async function installCloudWorkspace(input) {
5890
5992
  input.adoptMaterialization(intent.workspaceId);
5891
5993
  await onStage?.("rows-committed");
5892
5994
  renameSync(intent.stagingPath, intent.destinationPath);
5995
+ clearAddStagingEvidence(database, intent.workspaceId);
5893
5996
  refreshRevealedRootEvidence(database, intent.workspaceId, intent.destinationPath);
5894
5997
  await onStage?.("ground-revealed");
5895
5998
  ensureWorkspaceBinding({