@amalgm/shell 0.1.133 → 0.1.134

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, hasSealedContent, hashContentFile, pruneSettledContentCache, sealCapturedArtifact, } from "./content-cache-host.js";
14
+ import { ContentCacheDownloadSet, captureContentFile, contentBlockCollector, hasSealedContent, hashContentFile, pruneSettledContentCache, sealCapturedArtifact, } from "./content-cache-host.js";
15
+ import { RegisterUploadFusion } from "./register-upload-fusion.js";
15
16
  import { StagedContentSet, addStagingEvidenceDir, clearAddStagingEvidence, } from "./workspace-add-staging.js";
16
17
  import { DirectContentPlane } from "./content-direct.js";
17
18
  import { encodeContentWireBytes } from "./content-wire-codec.js";
@@ -676,20 +677,75 @@ export class UserGroundHost {
676
677
  this.stage({
677
678
  primitive: "register", stage: "entity-registration", status: "started", workspaceId,
678
679
  });
679
- const scanned = await reconcileGround({
680
- identity,
681
- userRoot: this.userRoot(identity),
682
- database: this.databasePath(identity),
683
- cacheDir: this.cacheDir(identity),
684
- onScan: (evidence) => this.reportDetectScan(evidence),
685
- measuring: this.measuring(),
686
- registrationWorkspaceId: workspaceId,
687
- suspicions: new Map(observations.map((observation) => [observation.directory, observation.suspicion])),
680
+ // The walk's own read is also the upload: smalls ride cargo
681
+ // segments, larges stream blocks and seal last. The flush settles
682
+ // every fused effect before publication plans, so the upload
683
+ // stage's seal inventory — the guarantor for anything fusion
684
+ // could not ship — already sees these seals.
685
+ const fusionResourceId = this.cloudState.resourceId;
686
+ const fusionLegs = new TransferLegs();
687
+ const fusion = new RegisterUploadFusion({
688
+ sealedContentHashes: (hashes) => this.sealedContentHashes(fusionResourceId, hashes),
689
+ sendCargo: async (artifacts, payload) => {
690
+ const { sealed } = await this.putCargo(fusionResourceId, artifacts, payload, fusionLegs);
691
+ return sealed;
692
+ },
693
+ putBlocks: async (chunks, bytes) => {
694
+ await this.putContentBlocks(fusionResourceId, chunks, bytes, fusionLegs);
695
+ },
696
+ sealManifest: async (manifest) => {
697
+ await this.putContentManifest(fusionResourceId, manifest, fusionLegs);
698
+ },
688
699
  });
700
+ const scanned = await (async () => {
701
+ try {
702
+ return await reconcileGround({
703
+ identity,
704
+ userRoot: this.userRoot(identity),
705
+ database: this.databasePath(identity),
706
+ cacheDir: this.cacheDir(identity),
707
+ onScan: (evidence) => this.reportDetectScan(evidence),
708
+ measuring: this.measuring(),
709
+ registrationWorkspaceId: workspaceId,
710
+ suspicions: new Map(observations.map((observation) => [observation.directory, observation.suspicion])),
711
+ fusion,
712
+ });
713
+ }
714
+ finally {
715
+ // Even an abandoned walk settles its in-flight cargo, and a
716
+ // stale-capture re-walk then skips whatever already sealed.
717
+ const fused = await fusion.flush();
718
+ this.stage({
719
+ primitive: "register",
720
+ stage: "fused-upload",
721
+ status: "completed",
722
+ workspaceId,
723
+ measurements: {
724
+ smallArtifacts: fused.smallArtifacts,
725
+ smallBytes: fused.smallBytes,
726
+ cargoSegments: fused.cargoSegments,
727
+ sealedSkipped: fused.sealedSkipped,
728
+ largeArtifacts: fused.largeArtifacts,
729
+ largeBytes: fused.largeBytes,
730
+ ...(fused.disabledReason !== null ? { disabled: fused.disabledReason } : {}),
731
+ ...fusionLegs.report(),
732
+ },
733
+ });
734
+ }
735
+ })();
689
736
  const retry = scanned.detection.find((plan) => plan.kind === "retry");
690
737
  if (retry?.kind === "retry") {
691
738
  throw new Error(retry.reasons.join("; "));
692
739
  }
740
+ // The classic upload re-proved every source byte against its
741
+ // captured hash when it read it. The fused rail already shipped
742
+ // those bytes, so the equivalent proof is the fingerprint law at
743
+ // stat cost: any fused source that moved after capture makes this
744
+ // attempt stale, and the re-walk publishes what stands there now.
745
+ const movedGround = await fusion.movedGround();
746
+ if (movedGround !== null) {
747
+ throw new StaleCaptureError(`${movedGround} changed after its fused upload`);
748
+ }
693
749
  const registrationDuration = performance.now() - registrationStarted;
694
750
  const commitStarted = performance.now();
695
751
  this.stage({
@@ -867,10 +923,10 @@ export class UserGroundHost {
867
923
  throw new Error("entity Record rail already started");
868
924
  await this.wire.open();
869
925
  // One floor, no per-feature gates: this shell and its gateway release
870
- // together, and the gateway ships first. 14 = immutable content packs
871
- // with a refs frame answering locations; an older gateway still speaks
872
- // presign and cannot resolve a packed object at all.
873
- if (this.wire.protocolVersion < 14) {
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) {
874
930
  throw new Error("gateway speaks an older wire protocol than this shell");
875
931
  }
876
932
  this.adoptCloudDeliveryBaseline(identity, this.cloudState.deliveryWatermark);
@@ -1413,6 +1469,40 @@ export class UserGroundHost {
1413
1469
  wireBytes: encoded.bytes.byteLength,
1414
1470
  };
1415
1471
  }
1472
+ /** Store one wave of provisional content blocks, addressed by their own
1473
+ * digests and bound to no artifact. Resolving means every block is
1474
+ * durable at the authority. Answers the wire bytes actually sent. */
1475
+ async putContentBlocks(resourceId, blocks, payload, legs) {
1476
+ const encoded = await legs.time("encodeMs", () => encodeContentWireBytes(payload));
1477
+ const frame = await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1478
+ type: "private.entity-content.put-blocks",
1479
+ resource_id: resourceId,
1480
+ blocks,
1481
+ bytes: payload.byteLength,
1482
+ wire_bytes: encoded.bytes.byteLength,
1483
+ ...(encoded.encoding ? { content_encoding: encoded.encoding } : {}),
1484
+ }, encoded.bytes, ["private.entity-content.stored-blocks"])));
1485
+ legs.add("gatewayVerifyMs", Number(frame.verify_ms));
1486
+ legs.add("gatewayStoreMs", Number(frame.store_ms));
1487
+ return encoded.bytes.byteLength;
1488
+ }
1489
+ /** Seal one artifact's manifest. The authority re-proves every named
1490
+ * block durable before anything becomes visible, so this frame is only
1491
+ * lawful after the complete file went over the wire. */
1492
+ async putContentManifest(resourceId, manifest, legs) {
1493
+ const bytes = Buffer.from(stableJson(manifest), "utf8");
1494
+ const frame = await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1495
+ type: "private.entity-content.put",
1496
+ resource_id: resourceId,
1497
+ content_hash: manifest.contentHash,
1498
+ sha256: sha256Hex(bytes),
1499
+ bytes: bytes.length,
1500
+ }, bytes, ["private.entity-content.stored"])));
1501
+ legs.add("gatewayVerifyMs", Number(frame.verify_ms));
1502
+ legs.add("gatewayStoreMs", Number(frame.store_ms));
1503
+ legs.add("gatewaySealMs", Number(frame.seal_ms));
1504
+ return bytes.length;
1505
+ }
1416
1506
  async uploadSnapshotContent(identity, resourceId, records, trace, manifestHints) {
1417
1507
  const cache = this.cacheDir(identity);
1418
1508
  const byHash = new Map();
@@ -1550,22 +1640,12 @@ export class UserGroundHost {
1550
1640
  const { artifact, file, manifest: knownManifest, bytes: fileBytes } = source;
1551
1641
  const sourceFile = { handle: null };
1552
1642
  let manifestPresent = false;
1553
- const putChunk = async (manifest, index, bytes) => {
1643
+ const chunkBlocks = (manifest, indexes) => indexes.map((index) => {
1554
1644
  const chunk = manifest.chunks[index];
1555
1645
  if (!chunk)
1556
1646
  throw new Error(`content manifest has no chunk ${index}`);
1557
- await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1558
- type: "private.entity-content.put",
1559
- resource_id: resourceId,
1560
- content_hash: artifact.contentHash,
1561
- kind: "chunk",
1562
- part_index: index,
1563
- part_count: manifest.chunks.length,
1564
- sha256: chunk.sha256,
1565
- bytes: chunk.bytes,
1566
- }, bytes, ["private.entity-content.stored"])));
1567
- uploadedBytes += bytes.byteLength;
1568
- };
1647
+ return { sha256: chunk.sha256, bytes: chunk.bytes };
1648
+ });
1569
1649
  try {
1570
1650
  const receipt = await uploadArtifact(artifact, {
1571
1651
  bytes: fileBytes,
@@ -1595,40 +1675,16 @@ export class UserGroundHost {
1595
1675
  }
1596
1676
  return frame.missing;
1597
1677
  },
1598
- putChunk: async (_candidate, manifest, index, bytes) => putChunk(manifest, index, bytes),
1678
+ putChunk: async (_candidate, manifest, index, bytes) => {
1679
+ uploadedBytes += await this.putContentBlocks(resourceId, chunkBlocks(manifest, [index]), bytes, legs);
1680
+ },
1599
1681
  putChunkBatch: async (_candidate, manifest, indexes, bytes) => {
1600
- const encoded = await legs.time("encodeMs", () => encodeContentWireBytes(bytes));
1601
- await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1602
- type: "private.entity-content.put-batch",
1603
- resource_id: resourceId,
1604
- content_hash: artifact.contentHash,
1605
- part_count: manifest.chunks.length,
1606
- chunks: indexes.map((index) => ({
1607
- part_index: index,
1608
- sha256: manifest.chunks[index]?.sha256,
1609
- bytes: manifest.chunks[index]?.bytes,
1610
- })),
1611
- bytes: bytes.byteLength,
1612
- wire_bytes: encoded.bytes.byteLength,
1613
- ...(encoded.encoding ? { content_encoding: encoded.encoding } : {}),
1614
- }, encoded.bytes, ["private.entity-content.stored-batch"])));
1615
- uploadedBytes += encoded.bytes.byteLength;
1682
+ uploadedBytes += await this.putContentBlocks(resourceId, chunkBlocks(manifest, indexes), bytes, legs);
1616
1683
  },
1617
1684
  putManifest: async (_candidate, manifest) => {
1618
1685
  if (manifestPresent)
1619
1686
  return;
1620
- const bytes = Buffer.from(stableJson(manifest), "utf8");
1621
- await legs.time("wireMs", () => retryContent(() => this.wire.requestBinary({
1622
- type: "private.entity-content.put",
1623
- resource_id: resourceId,
1624
- content_hash: artifact.contentHash,
1625
- kind: "manifest",
1626
- part_index: manifest.chunks.length,
1627
- part_count: manifest.chunks.length,
1628
- sha256: sha256Hex(bytes),
1629
- bytes: bytes.length,
1630
- }, bytes, ["private.entity-content.stored"])));
1631
- uploadedBytes += bytes.byteLength;
1687
+ uploadedBytes += await this.putContentManifest(resourceId, manifest, legs);
1632
1688
  },
1633
1689
  });
1634
1690
  uploadedChunks += receipt.uploadedChunks;
@@ -4102,7 +4158,7 @@ function materializedWorkspaceBindings(bindingDir) {
4102
4158
  return bindings.sort((left, right) => left.workspaceId.localeCompare(right.workspaceId));
4103
4159
  }
4104
4160
  async function reconcileGround(input) {
4105
- const { identity, userRoot, database, cacheDir, suspicions, coreScan, registrationWorkspaceId, onScan, measuring = false, } = input;
4161
+ const { identity, userRoot, database, cacheDir, suspicions, coreScan, registrationWorkspaceId, fusion, onScan, measuring = false, } = input;
4106
4162
  const resourceId = privateEntityResourceId(identity.userId, sha256Hex);
4107
4163
  const bindingDir = workspaceBindingDir(userRoot, identity.deviceId);
4108
4164
  const boundRootEntries = materializedWorkspaceBindings(bindingDir);
@@ -4328,9 +4384,11 @@ async function reconcileGround(input) {
4328
4384
  suspicion: observationFor(rootPath),
4329
4385
  // A register scan publishes one complete snapshot; it never mints Send
4330
4386
  // records for the same initial contents, so it plans no detection and
4331
- // stages no cache bytes — upload reads the ground itself.
4387
+ // stages no cache bytes — the fused rail ships content from the walk's
4388
+ // own read, and upload re-reads only what fusion could not seal.
4332
4389
  plansSendRecords: suspicions !== undefined && registrationWorkspaceId === undefined,
4333
4390
  largeManifests,
4391
+ fusion,
4334
4392
  });
4335
4393
  }
4336
4394
  const changedUUIDs = new Set([
@@ -4358,7 +4416,7 @@ async function reconcileGround(input) {
4358
4416
  };
4359
4417
  }
4360
4418
  async function reconcileRoot(input) {
4361
- const { identity, resourceId, rootUUID, rootName, rootPath, rootType, database, cacheDir, policy, bindingDir, suspects = null, evidence, measureEvidence = false, plansSendRecords, largeManifests, } = input;
4419
+ const { identity, resourceId, rootUUID, rootName, rootPath, rootType, database, cacheDir, policy, bindingDir, suspects = null, evidence, measureEvidence = false, plansSendRecords, largeManifests, fusion, } = input;
4362
4420
  // Prior rows reach this scan from every binding this root encloses. A row's
4363
4421
  // stored relativePath is relative to the root that last committed it, which
4364
4422
  // is a different frame when an enclosing boundary was just registered
@@ -4711,9 +4769,20 @@ async function reconcileRoot(input) {
4711
4769
  captureTasks.push(captureGate(async () => {
4712
4770
  const taskStarted = measureEvidence ? performance.now() : 0;
4713
4771
  let current = stats;
4772
+ // Repository leaves ship on the Git-native rail; everything else
4773
+ // may fuse its capture read with upload. Fusion is opportunistic:
4774
+ // once disabled, captures continue exactly as the classic path
4775
+ // and the upload stage ships what the walk could not.
4776
+ const fused = activeRepository ? undefined : fusion;
4714
4777
  for (let attempt = 1;; attempt++) {
4715
4778
  try {
4716
- const captured = await captureContentFile(absolutePath, current, activeRepository || !plansSendRecords ? null : cacheDir);
4779
+ const collect = fused?.enabled && current.size <= CHUNK_BYTES
4780
+ ? contentBlockCollector(current.size)
4781
+ : null;
4782
+ const captured = fused && current.size > CHUNK_BYTES
4783
+ ? (await fused.captureLarge(absolutePath, current))
4784
+ ?? await captureContentFile(absolutePath, current, activeRepository || !plansSendRecords ? null : cacheDir)
4785
+ : await captureContentFile(absolutePath, current, activeRepository || !plansSendRecords ? null : cacheDir, collect);
4717
4786
  if (measureEvidence && evidence) {
4718
4787
  evidence.captureBusyMs += performance.now() - taskStarted;
4719
4788
  evidence.captureBytes += captured.bytes;
@@ -4726,6 +4795,12 @@ async function reconcileRoot(input) {
4726
4795
  if (captured.bytes > CHUNK_BYTES) {
4727
4796
  largeManifests?.set(captured.contentHash, captured.manifest);
4728
4797
  }
4798
+ // The capture proved these exact bytes against the fingerprint
4799
+ // law; hand them to the cargo rail. Waiting here is window
4800
+ // backpressure, bounding memory by the capture pool's width.
4801
+ if (collect && fused) {
4802
+ await fused.addSmall(absolutePath, current, captured, collect.collected);
4803
+ }
4729
4804
  return;
4730
4805
  }
4731
4806
  catch (error) {