@amalgm/shell 0.1.93 → 0.1.95

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 } 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, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, download as downloadArtifact, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, privateEntityResourceId, repositoryIdentityHash, rootReplacementFromRecords, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } 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, createLocalEntityRecord, createEntityRecordAuthorityPort, convergeUserGround, createUserGroundEnrollmentPolicy, isRepositoryMetadataEntry, indexRepositoryTerritory, download as downloadArtifact, encodeEntityRecord, EntityApplyRail, EntityRecordRail, membershipHash, parseSnapshot, pathIsSuspect, PRESIGN_BATCH_OBJECTS, privateEntityResourceId, repositoryIdentityHash, rootReplacementFromRecords, sameRecords, snapshotFromRecords, stableJson, travelingRecords, upload as uploadArtifact, userGroundRecords, } 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";
@@ -12,7 +12,8 @@ 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
14
  import { ContentCacheDownload, captureContentFile, hashContentFile, sealCapturedArtifact, } from "./content-cache-host.js";
15
- import { decodeContentWireBytes, encodeContentWireBytes } from "./content-wire-codec.js";
15
+ import { DirectContentPlane } from "./content-direct.js";
16
+ import { encodeContentWireBytes } from "./content-wire-codec.js";
16
17
  import { exactTextReplay, planGroundDetection, reconcileGroundUUIDs, } from "./detection/portable.js";
17
18
  import { AcceptedInboxResultIndex } from "./detection/accepted-result.js";
18
19
  import { sameNotebookBasisRow } from "./detection/notebook-basis.js";
@@ -26,8 +27,13 @@ import { GroundCoordinator } from "./ground-coordination.js";
26
27
  import { submitMutationOperation } from "./paged-mutation-submit.js";
27
28
  import { WireClient, WireRequestError } from "./wire-client.js";
28
29
  const SMALL_CONTENT_UPLOAD_CONCURRENCY = 16;
29
- const FILE_CONTENT_DOWNLOAD_CONCURRENCY = 4;
30
- const FILE_BATCH_DOWNLOAD_CONCURRENCY = 2;
30
+ /** Artifact lanes for a download phase. Direct storage fetches cost the
31
+ * gateway nothing, so parallelism is bounded by this machine's pipe rather
32
+ * than authority fairness. */
33
+ const FILE_CONTENT_DOWNLOAD_CONCURRENCY = 16;
34
+ /** Block lanes within one artifact. Most files are a single block; large
35
+ * repository packs fan their chunks across this. */
36
+ const DIRECT_BLOCK_DOWNLOAD_CONCURRENCY = 8;
31
37
  const DETECT_QUIET_MS = 12;
32
38
  const DETECT_MAX_DEFERRAL_MS = 75;
33
39
  const DETECT_RETRY_MS = 250;
@@ -47,6 +53,7 @@ const GROUND_ROW_COLUMNS = [
47
53
  export class UserGroundHost {
48
54
  options;
49
55
  wire;
56
+ direct;
50
57
  watchHost;
51
58
  activeIdentity = null;
52
59
  rescanTimer = null;
@@ -75,6 +82,9 @@ export class UserGroundHost {
75
82
  runtimeVersion: options.runtimeVersion,
76
83
  requestTimeoutMs: options.wireRequestTimeoutMs,
77
84
  });
85
+ this.direct = new DirectContentPlane({
86
+ request: (frame, accept) => this.wire.request(frame, accept),
87
+ });
78
88
  this.watchHost = new NodeWatchHost({
79
89
  open: options.openWatch,
80
90
  onSignal: () => this.scheduleRescan(),
@@ -461,7 +471,17 @@ export class UserGroundHost {
461
471
  && record.payloadVersion === workspaceId);
462
472
  if (hasRegisteredBoundaryInDatabase(database, workspaceId)
463
473
  && hasRegisteredBoundary(this.cloudState.records)) {
464
- this.finishColdOperation();
474
+ // Already declared — but Register may only return once cargo closure
475
+ // holds under the current content contract. The graph head can be
476
+ // unchanged while a contract cutover moved its cargo namespace, so
477
+ // the publish path re-proves seal evidence and repairs any missing
478
+ // uploads before this answer is lawful.
479
+ try {
480
+ await this.publishMaterializedSnapshot(identity, new Set([workspaceId]), { journey: "register", workspaceId });
481
+ }
482
+ finally {
483
+ this.finishColdOperation();
484
+ }
465
485
  return {
466
486
  registered: true,
467
487
  entityCount: countDescendantRows(database, workspaceId),
@@ -670,10 +690,10 @@ export class UserGroundHost {
670
690
  throw new Error("entity Record rail already started");
671
691
  await this.wire.open();
672
692
  // One floor, no per-feature gates: this shell and its gateway release
673
- // together, and the gateway ships first. 8 = paged mutation operations;
674
- // an older gateway drops the stage frame and a large graph could never
675
- // publish.
676
- if (this.wire.protocolVersion < 8) {
693
+ // together, and the gateway ships first. 10 = contract-aware cargo
694
+ // closure; an older gateway drops the sealed-evidence frame the upload
695
+ // planner depends on, so nothing this shell registers could publish.
696
+ if (this.wire.protocolVersion < 10) {
677
697
  throw new Error("gateway speaks an older wire protocol than this shell");
678
698
  }
679
699
  const store = new EntityRecordSqliteStore(initializeDatabase(this.databasePath(identity)));
@@ -1220,42 +1240,13 @@ export class UserGroundHost {
1220
1240
  measurements: { journey: trace?.journey ?? "bootstrap", kind: artifact.kind },
1221
1241
  });
1222
1242
  const cache = { target: null };
1223
- // A cold artifactno local manifest — is fetched whole in one round
1224
- // trip: get-artifact answers with the manifest and, for artifacts within
1225
- // the batch caps, every block packed in part order. Chunk reads then
1226
- // slice this buffer instead of paying a second round trip.
1227
- const prefetched = { bytes: null };
1228
- const sliceChunks = (packed, manifest, indexes) => {
1229
- const offsets = [];
1230
- let offset = 0;
1231
- for (const chunk of manifest.chunks) {
1232
- offsets.push(offset);
1233
- offset += chunk.bytes;
1234
- }
1235
- return Buffer.concat(indexes.map((index) => {
1236
- const chunk = manifest.chunks[index];
1237
- const start = offsets[index];
1238
- if (!chunk || start === undefined)
1239
- throw new Error(`content manifest has no chunk ${index}`);
1240
- return packed.subarray(start, start + chunk.bytes);
1241
- }));
1242
- };
1243
- const getChunk = async (manifest, index) => {
1244
- if (prefetched.bytes)
1245
- return sliceChunks(prefetched.bytes, manifest, [index]);
1246
- const chunk = manifest.chunks[index];
1247
- if (!chunk)
1248
- throw new Error(`content manifest has no chunk ${index}`);
1243
+ // Every storage objectthe manifest and each block — is fetched
1244
+ // straight from content storage over a presigned URL and opened locally;
1245
+ // the wire carries only the key handoff and signing requests. A retry
1246
+ // asks for a fresh URL every attempt.
1247
+ const fetchObject = async (object) => {
1249
1248
  requests += 1;
1250
- const frame = await retryContent(() => this.wire.request({
1251
- type: "private.entity-content.get",
1252
- resource_id: resourceId,
1253
- content_hash: artifact.contentHash,
1254
- kind: "chunk",
1255
- part_index: index,
1256
- sha256: chunk.sha256,
1257
- }, ["private.entity-content.data"]), true, retryAttempts);
1258
- const bytes = binaryFrameBytes(frame);
1249
+ const bytes = await retryContent(() => this.direct.fetchObject(resourceId, object), true, retryAttempts);
1259
1250
  downloadedBytes += bytes.byteLength;
1260
1251
  return bytes;
1261
1252
  };
@@ -1267,73 +1258,39 @@ export class UserGroundHost {
1267
1258
  // proves chunk identity; downloaded bytes are still hash-verified,
1268
1259
  // so a stale local manifest can only fail loudly, never corrupt.
1269
1260
  const local = knownManifest ?? readCachedManifest(cacheDir, artifact.contentHash);
1270
- if (local) {
1271
- cache.target = new ContentCacheDownload(cacheDir, artifact, local);
1272
- return local;
1273
- }
1274
- requests += 1;
1275
- const frame = await retryContent(() => this.wire.request({
1276
- type: "private.entity-content.get-artifact",
1277
- resource_id: resourceId,
1278
- content_hash: artifact.contentHash,
1279
- }, ["private.entity-content.artifact"]), true, retryAttempts);
1280
- const manifestJson = String(frame.manifest_json ?? "");
1281
- downloadedBytes += Buffer.byteLength(manifestJson);
1282
- const checked = checkContentManifest(JSON.parse(manifestJson), artifact.contentHash);
1283
- if (!checked.ok)
1284
- throw new Error(checked.error);
1285
- if (frame.complete === true) {
1286
- const decoded = await decodeContentWireBytes(binaryWireFrameBytes(frame), frame.content_encoding, Number(frame.bytes));
1287
- if (decoded.byteLength !== checked.value.bytes) {
1288
- throw new Error("artifact payload does not match its manifest");
1289
- }
1290
- prefetched.bytes = decoded;
1291
- downloadedBytes += Number(frame.wire_bytes ?? frame.bytes);
1292
- }
1293
- cache.target = new ContentCacheDownload(cacheDir, artifact, checked.value);
1294
- return checked.value;
1261
+ const manifest = local ?? await (async () => {
1262
+ const bytes = await fetchObject({ kind: "manifest", sha256: artifact.contentHash });
1263
+ const checked = checkContentManifest(JSON.parse(bytes.toString("utf8")), artifact.contentHash);
1264
+ if (!checked.ok)
1265
+ throw new Error(checked.error);
1266
+ return checked.value;
1267
+ })();
1268
+ cache.target = new ContentCacheDownload(cacheDir, artifact, manifest);
1269
+ return manifest;
1295
1270
  },
1296
1271
  hasChunk: async (_candidate, _manifest, index) => {
1297
1272
  if (!cache.target)
1298
1273
  throw new Error("download cache has no content manifest");
1299
1274
  return cache.target.hasChunk(index);
1300
1275
  },
1301
- getChunk: async (_candidate, manifest, index) => getChunk(manifest, index),
1302
- getChunkBatch: async (_candidate, manifest, indexes) => {
1303
- if (prefetched.bytes)
1304
- return sliceChunks(prefetched.bytes, manifest, indexes);
1305
- const frame = await retryContent(() => this.wire.request({
1306
- type: "private.entity-content.get-batch",
1307
- resource_id: resourceId,
1308
- content_hash: artifact.contentHash,
1309
- chunks: indexes.map((index) => ({
1310
- part_index: index,
1311
- sha256: manifest.chunks[index]?.sha256,
1312
- bytes: manifest.chunks[index]?.bytes,
1313
- })),
1314
- }, ["private.entity-content.data-batch"]), true, retryAttempts);
1315
- requests += 1;
1316
- const decoded = await decodeContentWireBytes(binaryWireFrameBytes(frame), frame.content_encoding, Number(frame.bytes));
1317
- downloadedBytes += Number(frame.wire_bytes ?? frame.bytes);
1318
- return decoded;
1276
+ getChunk: async (_candidate, manifest, index) => {
1277
+ const chunk = manifest.chunks[index];
1278
+ if (!chunk)
1279
+ throw new Error(`content manifest has no chunk ${index}`);
1280
+ return fetchObject({ kind: "block", sha256: chunk.sha256 });
1319
1281
  },
1320
1282
  putChunk: async (_candidate, _manifest, index, bytes) => {
1321
1283
  if (!cache.target)
1322
1284
  throw new Error("download cache has no content manifest");
1323
1285
  await cache.target.putChunk(index, bytes);
1324
1286
  },
1325
- putChunkBatch: async (_candidate, _manifest, indexes, bytes) => {
1326
- if (!cache.target)
1327
- throw new Error("download cache has no content manifest");
1328
- await cache.target.putChunkBatch(indexes, bytes);
1329
- },
1330
1287
  sealArtifact: async () => {
1331
1288
  if (!cache.target)
1332
1289
  throw new Error("download cache has no content manifest");
1333
1290
  const sealed = await cache.target.seal();
1334
1291
  return { local: sealed, bytes: sealed.bytes, contentHash: sealed.contentHash };
1335
1292
  },
1336
- }, artifact.kind === "file" ? { concurrency: FILE_BATCH_DOWNLOAD_CONCURRENCY } : {});
1293
+ }, { concurrency: DIRECT_BLOCK_DOWNLOAD_CONCURRENCY });
1337
1294
  this.stage({
1338
1295
  primitive: "download",
1339
1296
  stage: "immutable-content",
@@ -1757,9 +1714,37 @@ export class UserGroundHost {
1757
1714
  const snapshot = snapshotFromRecords(traveling);
1758
1715
  const snapshotJson = stableJson(snapshot);
1759
1716
  const checksum = sha256Hex(snapshotJson);
1760
- if (checksum === state.checksum)
1761
- return;
1762
1717
  const localIds = new Set(localRecords.map((record) => record.uuid));
1718
+ // Graph membership is never proof of sealed cargo: a record in the cloud
1719
+ // graph may name content sealed only under a prior content contract,
1720
+ // whose namespace the current readers never touch. Only the authority's
1721
+ // (resource, hash, contract) seal evidence may skip an upload — and the
1722
+ // question is asked even when the graph itself is unchanged, so Register
1723
+ // always re-establishes cargo closure before returning.
1724
+ const cargo = snapshot.records.flatMap((record) => {
1725
+ if (!localIds.has(record.uuid))
1726
+ return [];
1727
+ const artifact = artifactForRecord(record);
1728
+ return artifact ? [{ record, contentHash: artifact.contentHash }] : [];
1729
+ });
1730
+ const sealedArtifacts = await this.sealedContentHashes(state.resourceId, [...new Set(cargo.map((entry) => entry.contentHash))]);
1731
+ const uploadRecords = cargo
1732
+ .filter((entry) => !sealedArtifacts.has(entry.contentHash))
1733
+ .map((entry) => entry.record);
1734
+ if (checksum === state.checksum) {
1735
+ // The graph is already committed; only cargo can be missing. Repair
1736
+ // uploads need no journal row — a crash simply re-runs the same
1737
+ // repair on the next Register.
1738
+ if (uploadRecords.length > 0) {
1739
+ try {
1740
+ await this.uploadSnapshotContent(identity, state.resourceId, uploadRecords, trace);
1741
+ }
1742
+ catch (error) {
1743
+ throw pipelineStageError("cloud upload", error);
1744
+ }
1745
+ }
1746
+ return;
1747
+ }
1763
1748
  const replacementRootIds = localRecords
1764
1749
  .filter((record) => record.parentUUID === null)
1765
1750
  .map((record) => record.uuid);
@@ -1770,16 +1755,6 @@ export class UserGroundHost {
1770
1755
  return root !== null && replacementRoots.has(root);
1771
1756
  });
1772
1757
  const replacement = rootReplacementFromRecords(replacementRootIds, replacementRecords);
1773
- const publishedArtifacts = new Set(state.records.flatMap((record) => {
1774
- const artifact = artifactForRecord(record);
1775
- return artifact ? [artifact.contentHash] : [];
1776
- }));
1777
- const uploadRecords = snapshot.records.filter((record) => {
1778
- if (!localIds.has(record.uuid))
1779
- return false;
1780
- const artifact = artifactForRecord(record);
1781
- return artifact !== null && !publishedArtifacts.has(artifact.contentHash);
1782
- });
1783
1758
  supersedeSnapshotJournal(this.databasePath(identity), {
1784
1759
  mutationId: randomUUID(),
1785
1760
  resourceId: state.resourceId,
@@ -1794,6 +1769,29 @@ export class UserGroundHost {
1794
1769
  });
1795
1770
  await this.publishPendingSnapshots(identity, trace);
1796
1771
  }
1772
+ /** Which of these heads does the authority hold sealed under the current
1773
+ * content contract? Batched so a whole workspace costs a handful of
1774
+ * frames; the answer is the only permission to skip an upload. */
1775
+ async sealedContentHashes(resourceId, hashes) {
1776
+ const batches = [];
1777
+ for (let start = 0; start < hashes.length; start += PRESIGN_BATCH_OBJECTS) {
1778
+ batches.push(hashes.slice(start, start + PRESIGN_BATCH_OBJECTS));
1779
+ }
1780
+ const sealed = new Set();
1781
+ const answers = await Promise.all(batches.map((batch) => retryContent(() => this.wire.request({
1782
+ type: "private.entity-content.sealed",
1783
+ resource_id: resourceId,
1784
+ content_hashes: batch,
1785
+ }, ["private.entity-content.sealed-result"]))));
1786
+ for (const frame of answers) {
1787
+ if (!Array.isArray(frame.sealed)) {
1788
+ throw new Error("content authority answered an invalid seal inventory");
1789
+ }
1790
+ for (const hash of frame.sealed)
1791
+ sealed.add(String(hash));
1792
+ }
1793
+ return sealed;
1794
+ }
1797
1795
  async publishPendingSnapshots(identity, trace) {
1798
1796
  for (const pending of readSnapshotPublications(this.databasePath(identity))) {
1799
1797
  // A rejected row is a settled fact, not an intent: the authority
@@ -4689,28 +4687,6 @@ function readyFromFrame(frame) {
4689
4687
  },
4690
4688
  };
4691
4689
  }
4692
- /** Expose the WebSocket payload as a view. Live verifies content blocks
4693
- * against their manifest, so the host must not copy or hash those bytes a
4694
- * second time before returning them to the portable operation. */
4695
- function binaryFrameBytes(frame) {
4696
- const bytes = binaryWireFrameBytes(frame);
4697
- if (frame.content_encoding !== undefined || bytes.length !== Number(frame.bytes)) {
4698
- throw new Error("wire content byte count does not match its header");
4699
- }
4700
- return bytes;
4701
- }
4702
- function binaryWireFrameBytes(frame) {
4703
- if (!(frame.data_bin instanceof Uint8Array)) {
4704
- throw new Error("wire content response is not a binary frame");
4705
- }
4706
- const bytes = Buffer.isBuffer(frame.data_bin)
4707
- ? frame.data_bin
4708
- : Buffer.from(frame.data_bin.buffer, frame.data_bin.byteOffset, frame.data_bin.byteLength);
4709
- if (bytes.length !== Number(frame.wire_bytes ?? frame.bytes)) {
4710
- throw new Error("wire content byte count does not match its header");
4711
- }
4712
- return bytes;
4713
- }
4714
4690
  /** Immutable content reads and writes are idempotent. Retry transient wire or
4715
4691
  * authority failures; reads additionally tolerate the brief not-found window
4716
4692
  * between an immutable object acknowledgement and globally visible R2 state. */