amalgm 0.1.238 → 0.1.240
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/lib/shared-realtime-tunnel.js +22 -14
- package/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +68 -20
- package/runtime/scripts/amalgm-mcp/project-context/store.js +87 -19
- package/runtime/scripts/amalgm-mcp/state/attachments.js +40 -12
- package/runtime/scripts/amalgm-mcp/state/db.js +216 -17
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +550 -0
- package/runtime/scripts/amalgm-mcp/state/docs.js +1101 -301
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +99 -0
- package/runtime/scripts/amalgm-mcp/state/mutations.js +507 -130
- package/runtime/scripts/amalgm-mcp/state/promotions.js +21 -1
- package/runtime/scripts/amalgm-mcp/state/replicas.js +134 -13
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +14 -8
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +193 -0
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +313 -94
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +1 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +32 -17
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +84 -0
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +376 -13
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +309 -0
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +758 -30
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +72 -3
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +33 -6
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +27 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +334 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +24 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +1999 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +364 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +81 -3
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +114 -8
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +185 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +174 -269
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +26 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +2060 -407
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +119 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +1446 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +5319 -3
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +191 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +3140 -17
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +135 -637
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +5 -1
- package/runtime/scripts/amalgm-mcp/workspace/card.js +7 -7
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +36 -12
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +436 -173
- package/runtime/scripts/amalgm-mcp/workspace/content.js +32 -0
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +45 -0
- package/runtime/scripts/amalgm-mcp/workspace/git.js +391 -37
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +535 -0
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +89 -6
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +31 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +72 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +165 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +5 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +2 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +2201 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +19 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +18 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +18 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +33 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +47 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +43 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +114 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +84 -0
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +119 -54
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +616 -168
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +240 -0
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +1979 -570
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +49 -28
- package/runtime/scripts/amalgm-mcp/workspace/store.js +77 -4
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +240 -0
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +1729 -192
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +100 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +7 -1
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +73 -61
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +127 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +12 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +51 -6
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +125 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +79 -8
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +76 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +3480 -165
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +3766 -0
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +899 -71
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +352 -497
|
@@ -14,12 +14,17 @@ const fs = require('fs');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const { AMALGM_DIR } = require('../config');
|
|
16
16
|
const { appendStateEvent } = require('../state/events');
|
|
17
|
-
const
|
|
17
|
+
const durable = require('./durable');
|
|
18
18
|
const transition = require('./transition');
|
|
19
|
+
const {
|
|
20
|
+
CHUNK_BYTES,
|
|
21
|
+
describeBytes,
|
|
22
|
+
manifestChecksum,
|
|
23
|
+
sha256,
|
|
24
|
+
} = require('./content');
|
|
19
25
|
|
|
20
|
-
const CONTRACT = 'git-bundle-chunks@
|
|
21
|
-
const PAYLOAD_CONTRACT = 'opaque-chunks@
|
|
22
|
-
const CHUNK_BYTES = 512 * 1024;
|
|
26
|
+
const CONTRACT = 'git-bundle-chunks@2';
|
|
27
|
+
const PAYLOAD_CONTRACT = 'opaque-chunks@2';
|
|
23
28
|
const ROOT = path.join(AMALGM_DIR, 'state', 'repo-objects');
|
|
24
29
|
const MANIFESTS = path.join(ROOT, 'manifests');
|
|
25
30
|
const PAYLOADS = path.join(ROOT, 'payloads');
|
|
@@ -27,8 +32,16 @@ const CHUNKS = path.join(ROOT, 'chunks');
|
|
|
27
32
|
const REQUESTS = path.join(ROOT, 'requests');
|
|
28
33
|
const preparing = new Map();
|
|
29
34
|
|
|
30
|
-
function
|
|
31
|
-
return
|
|
35
|
+
function isProjectId(value) {
|
|
36
|
+
return require('./tree/identity').isUuid(String(value || ''));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function locatorChecksum(chunks) {
|
|
40
|
+
return manifestChecksum(chunks);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function locatorBytes(chunks) {
|
|
44
|
+
return chunks.reduce((total, chunk) => total + chunk.bytes, 0);
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
function ensureDirs() {
|
|
@@ -56,8 +69,7 @@ function readJson(file) {
|
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
function writeJson(file, value) {
|
|
59
|
-
|
|
60
|
-
fs.writeFileSync(file, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
|
72
|
+
durable.writeJson(file, value);
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
function publicLocator(manifest) {
|
|
@@ -83,14 +95,18 @@ function publicPayloadLocator(manifest) {
|
|
|
83
95
|
};
|
|
84
96
|
}
|
|
85
97
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
function describeChunks(bytes) {
|
|
99
|
+
return describeBytes(bytes).map((chunk) => ({ ...chunk, uploaded: false }));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function writeChunks(bytes, chunks = describeChunks(bytes)) {
|
|
103
|
+
let offset = 0;
|
|
104
|
+
for (const chunk of chunks) {
|
|
105
|
+
const part = bytes.subarray(offset, offset + chunk.bytes);
|
|
106
|
+
const chunkFile = path.join(CHUNKS, `${chunk.sha256}.bin`);
|
|
107
|
+
const held = fs.existsSync(chunkFile) ? fs.readFileSync(chunkFile) : null;
|
|
108
|
+
if (!held || sha256(held) !== chunk.sha256) durable.writeFile(chunkFile, part);
|
|
109
|
+
offset += chunk.bytes;
|
|
94
110
|
}
|
|
95
111
|
return chunks;
|
|
96
112
|
}
|
|
@@ -113,8 +129,8 @@ async function prepareBundle(worktreeRoot, repoKey, resourceId, oid, file) {
|
|
|
113
129
|
repoKey,
|
|
114
130
|
oid,
|
|
115
131
|
ref,
|
|
116
|
-
checksum:
|
|
117
|
-
bytes:
|
|
132
|
+
checksum: locatorChecksum(chunks),
|
|
133
|
+
bytes: locatorBytes(chunks),
|
|
118
134
|
chunks,
|
|
119
135
|
createdAt: new Date().toISOString(),
|
|
120
136
|
});
|
|
@@ -128,23 +144,29 @@ function publishPayload(key, bytesInput, repoKey) {
|
|
|
128
144
|
ensureDirs();
|
|
129
145
|
const bytes = Buffer.isBuffer(bytesInput) ? bytesInput : Buffer.from(bytesInput);
|
|
130
146
|
const project = require('./wall').projectForRepo(repoKey);
|
|
131
|
-
const checksum = sha256(bytes);
|
|
132
147
|
if (!project?.resourceId) {
|
|
133
|
-
return { kind: 'pending', contract: PAYLOAD_CONTRACT
|
|
148
|
+
return { kind: 'pending', contract: PAYLOAD_CONTRACT };
|
|
134
149
|
}
|
|
150
|
+
const chunks = describeChunks(bytes);
|
|
151
|
+
const checksum = locatorChecksum(chunks);
|
|
135
152
|
// Payload content is its identity. A later checkpoint that still contains
|
|
136
153
|
// the same large file must reference the already-uploaded bytes instead of
|
|
137
154
|
// creating another upload merely because the surrounding checkpoint moved.
|
|
138
155
|
const file = payloadFile(project.resourceId, checksum);
|
|
139
156
|
let manifest = readJson(file);
|
|
140
|
-
if (
|
|
157
|
+
if (
|
|
158
|
+
!manifest
|
|
159
|
+
|| manifest.contract !== PAYLOAD_CONTRACT
|
|
160
|
+
|| manifest.checksum !== checksum
|
|
161
|
+
) {
|
|
162
|
+
writeChunks(bytes, chunks);
|
|
141
163
|
manifest = {
|
|
142
164
|
contract: PAYLOAD_CONTRACT,
|
|
143
165
|
resourceId: project.resourceId,
|
|
144
166
|
repoKey,
|
|
145
167
|
checksum,
|
|
146
|
-
bytes:
|
|
147
|
-
chunks
|
|
168
|
+
bytes: locatorBytes(chunks),
|
|
169
|
+
chunks,
|
|
148
170
|
createdAt: new Date().toISOString(),
|
|
149
171
|
};
|
|
150
172
|
writeJson(file, manifest);
|
|
@@ -182,7 +204,6 @@ async function bundleSourceFor(worktreeRoot, repoKey, oid) {
|
|
|
182
204
|
|
|
183
205
|
async function objectSourceFor(worktreeRoot, repoKey, oid) {
|
|
184
206
|
if (!oid || !repoKey) return null;
|
|
185
|
-
if (await originContains(worktreeRoot, oid)) return { kind: 'remote' };
|
|
186
207
|
return bundleSourceFor(worktreeRoot, repoKey, oid);
|
|
187
208
|
}
|
|
188
209
|
|
|
@@ -190,10 +211,22 @@ function claimUpload() {
|
|
|
190
211
|
ensureDirs();
|
|
191
212
|
for (const directory of [MANIFESTS, PAYLOADS]) {
|
|
192
213
|
for (const name of fs.readdirSync(directory).sort()) {
|
|
193
|
-
const
|
|
214
|
+
const file = path.join(directory, name);
|
|
215
|
+
const manifest = readJson(file);
|
|
194
216
|
const chunk = manifest?.chunks?.find((entry) => !entry.uploaded);
|
|
195
217
|
if (!chunk) continue;
|
|
196
|
-
const
|
|
218
|
+
const chunkFile = path.join(CHUNKS, `${chunk.sha256}.bin`);
|
|
219
|
+
const bytes = fs.existsSync(chunkFile) ? fs.readFileSync(chunkFile) : null;
|
|
220
|
+
if (
|
|
221
|
+
!bytes
|
|
222
|
+
|| bytes.length !== chunk.bytes
|
|
223
|
+
|| sha256(bytes) !== chunk.sha256
|
|
224
|
+
) {
|
|
225
|
+
fs.rmSync(chunkFile, { force: true });
|
|
226
|
+
fs.rmSync(file, { force: true });
|
|
227
|
+
wake('delete', chunk.sha256);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
197
230
|
return {
|
|
198
231
|
resourceId: manifest.resourceId,
|
|
199
232
|
sha256: chunk.sha256,
|
|
@@ -243,17 +276,20 @@ function validatePayloadLocator(locator) {
|
|
|
243
276
|
if (
|
|
244
277
|
locator?.kind !== 'chunks'
|
|
245
278
|
|| locator?.contract !== PAYLOAD_CONTRACT
|
|
246
|
-
||
|
|
279
|
+
|| !isProjectId(locator.resourceId)
|
|
247
280
|
|| !/^[0-9a-f]{64}$/.test(String(locator.checksum || ''))
|
|
248
281
|
|| !Number.isSafeInteger(locator.bytes)
|
|
249
|
-
|| locator.bytes <
|
|
282
|
+
|| locator.bytes < 1
|
|
250
283
|
|| !Array.isArray(locator.chunks)
|
|
284
|
+
|| locator.chunks.length < 1
|
|
251
285
|
|| locator.chunks.some((chunk) => (
|
|
252
286
|
!/^[0-9a-f]{64}$/.test(String(chunk?.sha256 || ''))
|
|
253
287
|
|| !Number.isSafeInteger(chunk?.bytes)
|
|
254
|
-
|| chunk.bytes <
|
|
288
|
+
|| chunk.bytes < 1
|
|
255
289
|
|| chunk.bytes > CHUNK_BYTES
|
|
256
290
|
))
|
|
291
|
+
|| locatorBytes(locator.chunks) !== locator.bytes
|
|
292
|
+
|| locatorChecksum(locator.chunks) !== locator.checksum
|
|
257
293
|
) throw new Error('invalid checkpoint payload locator');
|
|
258
294
|
}
|
|
259
295
|
|
|
@@ -263,7 +299,11 @@ function readPayload(locator) {
|
|
|
263
299
|
let waiting = false;
|
|
264
300
|
for (const chunk of locator.chunks) {
|
|
265
301
|
const file = path.join(CHUNKS, `${chunk.sha256}.bin`);
|
|
266
|
-
if (fs.existsSync(file)
|
|
302
|
+
if (fs.existsSync(file)) {
|
|
303
|
+
const held = fs.readFileSync(file);
|
|
304
|
+
if (held.length === chunk.bytes && sha256(held) === chunk.sha256) continue;
|
|
305
|
+
fs.rmSync(file, { force: true });
|
|
306
|
+
}
|
|
267
307
|
writeJson(requestFile(locator.resourceId, chunk.sha256), {
|
|
268
308
|
resourceId: locator.resourceId,
|
|
269
309
|
sha256: chunk.sha256,
|
|
@@ -282,7 +322,7 @@ function readPayload(locator) {
|
|
|
282
322
|
return bytes;
|
|
283
323
|
});
|
|
284
324
|
const bytes = Buffer.concat(parts);
|
|
285
|
-
if (bytes.length !== locator.bytes
|
|
325
|
+
if (bytes.length !== locator.bytes) {
|
|
286
326
|
throw new Error('checkpoint payload verification failed');
|
|
287
327
|
}
|
|
288
328
|
writeJson(payloadFile(locator.resourceId, locator.checksum), {
|
|
@@ -303,21 +343,29 @@ function requestFile(resourceId, digest) {
|
|
|
303
343
|
|
|
304
344
|
function claimDownload() {
|
|
305
345
|
ensureDirs();
|
|
306
|
-
const name
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
346
|
+
for (const name of fs.readdirSync(REQUESTS).filter((entry) => entry.endsWith('.json')).sort()) {
|
|
347
|
+
const file = path.join(REQUESTS, name);
|
|
348
|
+
const request = readJson(file);
|
|
349
|
+
if (
|
|
350
|
+
request
|
|
351
|
+
&& Object.keys(request).length === 2
|
|
352
|
+
&& isProjectId(request.resourceId)
|
|
353
|
+
&& /^[0-9a-f]{64}$/.test(String(request.sha256 || ''))
|
|
354
|
+
) return request;
|
|
355
|
+
fs.rmSync(file, { force: true });
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
310
358
|
}
|
|
311
359
|
|
|
312
360
|
function receiveDownload(resourceId, digest, dataBase64) {
|
|
313
|
-
if (
|
|
361
|
+
if (!isProjectId(resourceId)) {
|
|
314
362
|
throw new Error('invalid repo object project');
|
|
315
363
|
}
|
|
316
364
|
if (!/^[0-9a-f]{64}$/.test(String(digest || ''))) throw new Error('invalid repo object checksum');
|
|
317
365
|
ensureDirs();
|
|
318
366
|
const bytes = Buffer.from(String(dataBase64 || ''), 'base64');
|
|
319
367
|
if (sha256(bytes) !== digest) throw new Error('repo object checksum mismatch');
|
|
320
|
-
|
|
368
|
+
durable.writeFile(path.join(CHUNKS, `${digest}.bin`), bytes);
|
|
321
369
|
fs.rmSync(requestFile(resourceId, digest), { force: true });
|
|
322
370
|
wake('update', digest);
|
|
323
371
|
require('./reducer').reduceNow().catch(() => {});
|
|
@@ -326,17 +374,24 @@ function receiveDownload(resourceId, digest, dataBase64) {
|
|
|
326
374
|
|
|
327
375
|
function validateLocator(locator) {
|
|
328
376
|
if (
|
|
329
|
-
locator?.
|
|
330
|
-
||
|
|
331
|
-
||
|
|
377
|
+
locator?.kind !== 'bundle'
|
|
378
|
+
|| locator?.contract !== CONTRACT
|
|
379
|
+
|| !isProjectId(locator.resourceId)
|
|
380
|
+
|| !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(String(locator.oid || ''))
|
|
332
381
|
|| !/^[0-9a-f]{64}$/.test(String(locator.checksum || ''))
|
|
382
|
+
|| locator.ref !== `refs/amalgm/exports/${locator.oid}`
|
|
383
|
+
|| !Number.isSafeInteger(locator.bytes)
|
|
384
|
+
|| locator.bytes < 1
|
|
333
385
|
|| !Array.isArray(locator.chunks)
|
|
386
|
+
|| locator.chunks.length < 1
|
|
334
387
|
|| locator.chunks.some((chunk) => (
|
|
335
388
|
!/^[0-9a-f]{64}$/.test(String(chunk?.sha256 || ''))
|
|
336
389
|
|| !Number.isSafeInteger(chunk?.bytes)
|
|
337
|
-
|| chunk.bytes <
|
|
390
|
+
|| chunk.bytes < 1
|
|
338
391
|
|| chunk.bytes > CHUNK_BYTES
|
|
339
392
|
))
|
|
393
|
+
|| locatorBytes(locator.chunks) !== locator.bytes
|
|
394
|
+
|| locatorChecksum(locator.chunks) !== locator.checksum
|
|
340
395
|
) throw new Error('invalid git bundle locator');
|
|
341
396
|
}
|
|
342
397
|
|
|
@@ -346,7 +401,11 @@ function prepareBundleImport(locator) {
|
|
|
346
401
|
let waiting = false;
|
|
347
402
|
for (const chunk of locator.chunks) {
|
|
348
403
|
const file = path.join(CHUNKS, `${chunk.sha256}.bin`);
|
|
349
|
-
if (fs.existsSync(file)
|
|
404
|
+
if (fs.existsSync(file)) {
|
|
405
|
+
const held = fs.readFileSync(file);
|
|
406
|
+
if (held.length === chunk.bytes && sha256(held) === chunk.sha256) continue;
|
|
407
|
+
fs.rmSync(file, { force: true });
|
|
408
|
+
}
|
|
350
409
|
writeJson(requestFile(locator.resourceId, chunk.sha256), {
|
|
351
410
|
resourceId: locator.resourceId,
|
|
352
411
|
sha256: chunk.sha256,
|
|
@@ -359,19 +418,24 @@ function prepareBundleImport(locator) {
|
|
|
359
418
|
}
|
|
360
419
|
|
|
361
420
|
const bundleFile = path.join(ROOT, `${locator.checksum}.bundle`);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
421
|
+
const parts = locator.chunks.map((chunk) => (
|
|
422
|
+
fs.readFileSync(path.join(CHUNKS, `${chunk.sha256}.bin`))
|
|
423
|
+
));
|
|
424
|
+
const bundleMatches = fs.existsSync(bundleFile) && (() => {
|
|
425
|
+
const held = fs.readFileSync(bundleFile);
|
|
426
|
+
let offset = 0;
|
|
427
|
+
for (let index = 0; index < locator.chunks.length; index += 1) {
|
|
428
|
+
const chunk = locator.chunks[index];
|
|
429
|
+
const part = held.subarray(offset, offset + chunk.bytes);
|
|
430
|
+
if (part.length !== chunk.bytes || sha256(part) !== chunk.sha256) return false;
|
|
431
|
+
offset += chunk.bytes;
|
|
373
432
|
}
|
|
374
|
-
|
|
433
|
+
return offset === held.length && held.length === locator.bytes;
|
|
434
|
+
})();
|
|
435
|
+
if (!bundleMatches) {
|
|
436
|
+
const bundle = Buffer.concat(parts);
|
|
437
|
+
if (bundle.length !== locator.bytes) throw new Error('git bundle verification failed');
|
|
438
|
+
durable.writeFile(bundleFile, bundle);
|
|
375
439
|
}
|
|
376
440
|
return { state: 'ready', file: bundleFile };
|
|
377
441
|
}
|
|
@@ -387,6 +451,7 @@ module.exports = {
|
|
|
387
451
|
CHUNK_BYTES,
|
|
388
452
|
CONTRACT,
|
|
389
453
|
PAYLOAD_CONTRACT,
|
|
454
|
+
_test: { describeChunks, locatorBytes, locatorChecksum },
|
|
390
455
|
acknowledgeUpload,
|
|
391
456
|
bundleSourceFor,
|
|
392
457
|
claimDownload,
|