@amalgm/shell 0.1.92 → 0.1.94
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.
- package/PURPOSE.md +27 -19
- package/README.md +2 -1
- package/dist/content-cache-host.d.ts +2 -4
- package/dist/content-cache-host.js +11 -37
- package/dist/content-cache-host.js.map +1 -1
- package/dist/content-direct.d.ts +31 -0
- package/dist/content-direct.js +101 -0
- package/dist/content-direct.js.map +1 -0
- package/dist/content-wire-codec.d.ts +3 -7
- package/dist/content-wire-codec.js +4 -25
- package/dist/content-wire-codec.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/paged-mutation-submit.d.ts +26 -0
- package/dist/paged-mutation-submit.js +52 -0
- package/dist/paged-mutation-submit.js.map +1 -0
- package/dist/user-ground-host.d.ts +1 -0
- package/dist/user-ground-host.js +49 -126
- package/dist/user-ground-host.js.map +1 -1
- package/dist/wire-client.d.ts +0 -1
- package/dist/wire-client.js +7 -16
- package/dist/wire-client.js.map +1 -1
- package/package.json +2 -2
package/dist/user-ground-host.js
CHANGED
|
@@ -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 {
|
|
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";
|
|
@@ -23,10 +24,16 @@ import { gitEvidenceFingerprint, inspectGitRegistration, sameGitIdentity, } from
|
|
|
23
24
|
import { projectMaterializedGraph } from "./materialized-graph.js";
|
|
24
25
|
import { NodeWatchHost, } from "./watching/index.js";
|
|
25
26
|
import { GroundCoordinator } from "./ground-coordination.js";
|
|
27
|
+
import { submitMutationOperation } from "./paged-mutation-submit.js";
|
|
26
28
|
import { WireClient, WireRequestError } from "./wire-client.js";
|
|
27
29
|
const SMALL_CONTENT_UPLOAD_CONCURRENCY = 16;
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
30
37
|
const DETECT_QUIET_MS = 12;
|
|
31
38
|
const DETECT_MAX_DEFERRAL_MS = 75;
|
|
32
39
|
const DETECT_RETRY_MS = 250;
|
|
@@ -46,6 +53,7 @@ const GROUND_ROW_COLUMNS = [
|
|
|
46
53
|
export class UserGroundHost {
|
|
47
54
|
options;
|
|
48
55
|
wire;
|
|
56
|
+
direct;
|
|
49
57
|
watchHost;
|
|
50
58
|
activeIdentity = null;
|
|
51
59
|
rescanTimer = null;
|
|
@@ -74,6 +82,9 @@ export class UserGroundHost {
|
|
|
74
82
|
runtimeVersion: options.runtimeVersion,
|
|
75
83
|
requestTimeoutMs: options.wireRequestTimeoutMs,
|
|
76
84
|
});
|
|
85
|
+
this.direct = new DirectContentPlane({
|
|
86
|
+
request: (frame, accept) => this.wire.request(frame, accept),
|
|
87
|
+
});
|
|
77
88
|
this.watchHost = new NodeWatchHost({
|
|
78
89
|
open: options.openWatch,
|
|
79
90
|
onSignal: () => this.scheduleRescan(),
|
|
@@ -669,9 +680,10 @@ export class UserGroundHost {
|
|
|
669
680
|
throw new Error("entity Record rail already started");
|
|
670
681
|
await this.wire.open();
|
|
671
682
|
// One floor, no per-feature gates: this shell and its gateway release
|
|
672
|
-
// together, and the gateway ships first.
|
|
673
|
-
//
|
|
674
|
-
|
|
683
|
+
// together, and the gateway ships first. 9 = direct content downloads;
|
|
684
|
+
// an older gateway still relays download bytes itself and drops the key
|
|
685
|
+
// and presign frames this shell depends on.
|
|
686
|
+
if (this.wire.protocolVersion < 9) {
|
|
675
687
|
throw new Error("gateway speaks an older wire protocol than this shell");
|
|
676
688
|
}
|
|
677
689
|
const store = new EntityRecordSqliteStore(initializeDatabase(this.databasePath(identity)));
|
|
@@ -1218,42 +1230,13 @@ export class UserGroundHost {
|
|
|
1218
1230
|
measurements: { journey: trace?.journey ?? "bootstrap", kind: artifact.kind },
|
|
1219
1231
|
});
|
|
1220
1232
|
const cache = { target: null };
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
// the
|
|
1224
|
-
//
|
|
1225
|
-
const
|
|
1226
|
-
const sliceChunks = (packed, manifest, indexes) => {
|
|
1227
|
-
const offsets = [];
|
|
1228
|
-
let offset = 0;
|
|
1229
|
-
for (const chunk of manifest.chunks) {
|
|
1230
|
-
offsets.push(offset);
|
|
1231
|
-
offset += chunk.bytes;
|
|
1232
|
-
}
|
|
1233
|
-
return Buffer.concat(indexes.map((index) => {
|
|
1234
|
-
const chunk = manifest.chunks[index];
|
|
1235
|
-
const start = offsets[index];
|
|
1236
|
-
if (!chunk || start === undefined)
|
|
1237
|
-
throw new Error(`content manifest has no chunk ${index}`);
|
|
1238
|
-
return packed.subarray(start, start + chunk.bytes);
|
|
1239
|
-
}));
|
|
1240
|
-
};
|
|
1241
|
-
const getChunk = async (manifest, index) => {
|
|
1242
|
-
if (prefetched.bytes)
|
|
1243
|
-
return sliceChunks(prefetched.bytes, manifest, [index]);
|
|
1244
|
-
const chunk = manifest.chunks[index];
|
|
1245
|
-
if (!chunk)
|
|
1246
|
-
throw new Error(`content manifest has no chunk ${index}`);
|
|
1233
|
+
// Every storage object — the manifest and each block — is fetched
|
|
1234
|
+
// straight from content storage over a presigned URL and opened locally;
|
|
1235
|
+
// the wire carries only the key handoff and signing requests. A retry
|
|
1236
|
+
// asks for a fresh URL every attempt.
|
|
1237
|
+
const fetchObject = async (object) => {
|
|
1247
1238
|
requests += 1;
|
|
1248
|
-
const
|
|
1249
|
-
type: "private.entity-content.get",
|
|
1250
|
-
resource_id: resourceId,
|
|
1251
|
-
content_hash: artifact.contentHash,
|
|
1252
|
-
kind: "chunk",
|
|
1253
|
-
part_index: index,
|
|
1254
|
-
sha256: chunk.sha256,
|
|
1255
|
-
}, ["private.entity-content.data"]), true, retryAttempts);
|
|
1256
|
-
const bytes = binaryFrameBytes(frame);
|
|
1239
|
+
const bytes = await retryContent(() => this.direct.fetchObject(resourceId, object), true, retryAttempts);
|
|
1257
1240
|
downloadedBytes += bytes.byteLength;
|
|
1258
1241
|
return bytes;
|
|
1259
1242
|
};
|
|
@@ -1265,73 +1248,39 @@ export class UserGroundHost {
|
|
|
1265
1248
|
// proves chunk identity; downloaded bytes are still hash-verified,
|
|
1266
1249
|
// so a stale local manifest can only fail loudly, never corrupt.
|
|
1267
1250
|
const local = knownManifest ?? readCachedManifest(cacheDir, artifact.contentHash);
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
}, ["private.entity-content.artifact"]), true, retryAttempts);
|
|
1278
|
-
const manifestJson = String(frame.manifest_json ?? "");
|
|
1279
|
-
downloadedBytes += Buffer.byteLength(manifestJson);
|
|
1280
|
-
const checked = checkContentManifest(JSON.parse(manifestJson), artifact.contentHash);
|
|
1281
|
-
if (!checked.ok)
|
|
1282
|
-
throw new Error(checked.error);
|
|
1283
|
-
if (frame.complete === true) {
|
|
1284
|
-
const decoded = await decodeContentWireBytes(binaryWireFrameBytes(frame), frame.content_encoding, Number(frame.bytes));
|
|
1285
|
-
if (decoded.byteLength !== checked.value.bytes) {
|
|
1286
|
-
throw new Error("artifact payload does not match its manifest");
|
|
1287
|
-
}
|
|
1288
|
-
prefetched.bytes = decoded;
|
|
1289
|
-
downloadedBytes += Number(frame.wire_bytes ?? frame.bytes);
|
|
1290
|
-
}
|
|
1291
|
-
cache.target = new ContentCacheDownload(cacheDir, artifact, checked.value);
|
|
1292
|
-
return checked.value;
|
|
1251
|
+
const manifest = local ?? await (async () => {
|
|
1252
|
+
const bytes = await fetchObject({ kind: "manifest", sha256: artifact.contentHash });
|
|
1253
|
+
const checked = checkContentManifest(JSON.parse(bytes.toString("utf8")), artifact.contentHash);
|
|
1254
|
+
if (!checked.ok)
|
|
1255
|
+
throw new Error(checked.error);
|
|
1256
|
+
return checked.value;
|
|
1257
|
+
})();
|
|
1258
|
+
cache.target = new ContentCacheDownload(cacheDir, artifact, manifest);
|
|
1259
|
+
return manifest;
|
|
1293
1260
|
},
|
|
1294
1261
|
hasChunk: async (_candidate, _manifest, index) => {
|
|
1295
1262
|
if (!cache.target)
|
|
1296
1263
|
throw new Error("download cache has no content manifest");
|
|
1297
1264
|
return cache.target.hasChunk(index);
|
|
1298
1265
|
},
|
|
1299
|
-
getChunk: async (_candidate, manifest, index) =>
|
|
1300
|
-
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
type: "private.entity-content.get-batch",
|
|
1305
|
-
resource_id: resourceId,
|
|
1306
|
-
content_hash: artifact.contentHash,
|
|
1307
|
-
chunks: indexes.map((index) => ({
|
|
1308
|
-
part_index: index,
|
|
1309
|
-
sha256: manifest.chunks[index]?.sha256,
|
|
1310
|
-
bytes: manifest.chunks[index]?.bytes,
|
|
1311
|
-
})),
|
|
1312
|
-
}, ["private.entity-content.data-batch"]), true, retryAttempts);
|
|
1313
|
-
requests += 1;
|
|
1314
|
-
const decoded = await decodeContentWireBytes(binaryWireFrameBytes(frame), frame.content_encoding, Number(frame.bytes));
|
|
1315
|
-
downloadedBytes += Number(frame.wire_bytes ?? frame.bytes);
|
|
1316
|
-
return decoded;
|
|
1266
|
+
getChunk: async (_candidate, manifest, index) => {
|
|
1267
|
+
const chunk = manifest.chunks[index];
|
|
1268
|
+
if (!chunk)
|
|
1269
|
+
throw new Error(`content manifest has no chunk ${index}`);
|
|
1270
|
+
return fetchObject({ kind: "block", sha256: chunk.sha256 });
|
|
1317
1271
|
},
|
|
1318
1272
|
putChunk: async (_candidate, _manifest, index, bytes) => {
|
|
1319
1273
|
if (!cache.target)
|
|
1320
1274
|
throw new Error("download cache has no content manifest");
|
|
1321
1275
|
await cache.target.putChunk(index, bytes);
|
|
1322
1276
|
},
|
|
1323
|
-
putChunkBatch: async (_candidate, _manifest, indexes, bytes) => {
|
|
1324
|
-
if (!cache.target)
|
|
1325
|
-
throw new Error("download cache has no content manifest");
|
|
1326
|
-
await cache.target.putChunkBatch(indexes, bytes);
|
|
1327
|
-
},
|
|
1328
1277
|
sealArtifact: async () => {
|
|
1329
1278
|
if (!cache.target)
|
|
1330
1279
|
throw new Error("download cache has no content manifest");
|
|
1331
1280
|
const sealed = await cache.target.seal();
|
|
1332
1281
|
return { local: sealed, bytes: sealed.bytes, contentHash: sealed.contentHash };
|
|
1333
1282
|
},
|
|
1334
|
-
},
|
|
1283
|
+
}, { concurrency: DIRECT_BLOCK_DOWNLOAD_CONCURRENCY });
|
|
1335
1284
|
this.stage({
|
|
1336
1285
|
primitive: "download",
|
|
1337
1286
|
stage: "immutable-content",
|
|
@@ -1819,20 +1768,16 @@ export class UserGroundHost {
|
|
|
1819
1768
|
});
|
|
1820
1769
|
}
|
|
1821
1770
|
try {
|
|
1822
|
-
frame = await this.wire
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
operationKind: replacement.kind,
|
|
1833
|
-
operation: replacement,
|
|
1834
|
-
},
|
|
1835
|
-
}, ["shared.mutation.ack"]);
|
|
1771
|
+
frame = await submitMutationOperation(this.wire, {
|
|
1772
|
+
resourceId: pending.resourceId,
|
|
1773
|
+
authorityEpoch: pending.authorityEpoch,
|
|
1774
|
+
mutationId: pending.mutationId,
|
|
1775
|
+
deviceId: identity.deviceId,
|
|
1776
|
+
baseVersion: pending.baseVersion,
|
|
1777
|
+
contract: ENTITY_CLOUD_CONTRACT,
|
|
1778
|
+
schemaVersion: ENTITY_CLOUD_SCHEMA_VERSION,
|
|
1779
|
+
operationKind: replacement.kind,
|
|
1780
|
+
}, replacement);
|
|
1836
1781
|
}
|
|
1837
1782
|
catch (error) {
|
|
1838
1783
|
if (trace?.journey === "register") {
|
|
@@ -4691,28 +4636,6 @@ function readyFromFrame(frame) {
|
|
|
4691
4636
|
},
|
|
4692
4637
|
};
|
|
4693
4638
|
}
|
|
4694
|
-
/** Expose the WebSocket payload as a view. Live verifies content blocks
|
|
4695
|
-
* against their manifest, so the host must not copy or hash those bytes a
|
|
4696
|
-
* second time before returning them to the portable operation. */
|
|
4697
|
-
function binaryFrameBytes(frame) {
|
|
4698
|
-
const bytes = binaryWireFrameBytes(frame);
|
|
4699
|
-
if (frame.content_encoding !== undefined || bytes.length !== Number(frame.bytes)) {
|
|
4700
|
-
throw new Error("wire content byte count does not match its header");
|
|
4701
|
-
}
|
|
4702
|
-
return bytes;
|
|
4703
|
-
}
|
|
4704
|
-
function binaryWireFrameBytes(frame) {
|
|
4705
|
-
if (!(frame.data_bin instanceof Uint8Array)) {
|
|
4706
|
-
throw new Error("wire content response is not a binary frame");
|
|
4707
|
-
}
|
|
4708
|
-
const bytes = Buffer.isBuffer(frame.data_bin)
|
|
4709
|
-
? frame.data_bin
|
|
4710
|
-
: Buffer.from(frame.data_bin.buffer, frame.data_bin.byteOffset, frame.data_bin.byteLength);
|
|
4711
|
-
if (bytes.length !== Number(frame.wire_bytes ?? frame.bytes)) {
|
|
4712
|
-
throw new Error("wire content byte count does not match its header");
|
|
4713
|
-
}
|
|
4714
|
-
return bytes;
|
|
4715
|
-
}
|
|
4716
4639
|
/** Immutable content reads and writes are idempotent. Retry transient wire or
|
|
4717
4640
|
* authority failures; reads additionally tolerate the brief not-found window
|
|
4718
4641
|
* between an immutable object acknowledgement and globally visible R2 state. */
|