amalgm 0.1.245 → 0.1.246

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.
Files changed (157) hide show
  1. package/README.md +12 -0
  2. package/lib/cli.js +146 -8
  3. package/lib/layout.js +89 -4
  4. package/lib/shared-realtime-tunnel.js +384 -43
  5. package/lib/supervisor.js +0 -3
  6. package/package.json +2 -2
  7. package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
  8. package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
  9. package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
  10. package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
  11. package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
  12. package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
  13. package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
  14. package/runtime/scripts/amalgm-mcp/index.js +4 -0
  15. package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
  16. package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
  17. package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
  18. package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
  19. package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
  20. package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
  21. package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
  22. package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
  23. package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
  24. package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
  25. package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
  26. package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
  27. package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
  28. package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
  29. package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
  30. package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
  31. package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
  32. package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
  33. package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
  34. package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
  35. package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
  36. package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
  37. package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
  38. package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
  39. package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
  40. package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
  41. package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
  42. package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
  43. package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
  44. package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
  45. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
  46. package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
  47. package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
  48. package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
  49. package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
  50. package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
  51. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
  52. package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
  53. package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
  54. package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
  55. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
  56. package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
  57. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
  58. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
  59. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
  60. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
  61. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
  62. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
  63. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
  64. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
  65. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
  66. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
  67. package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
  68. package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
  69. package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
  70. package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
  71. package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
  72. package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
  73. package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
  74. package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
  75. package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
  76. package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
  77. package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
  78. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
  79. package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
  80. package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
  81. package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
  82. package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
  83. package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
  84. package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
  85. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
  86. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
  87. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
  88. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
  89. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
  90. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
  91. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
  92. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
  93. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
  94. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
  95. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
  96. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
  97. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
  98. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
  99. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
  100. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
  101. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
  102. package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
  103. package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
  104. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
  105. package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
  106. package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
  107. package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
  108. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
  109. package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
  110. package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
  111. package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
  112. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
  113. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
  114. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
  115. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
  116. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
  117. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
  118. package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
  119. package/runtime/scripts/local-gateway.js +1 -0
  120. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
  121. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
  122. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
  123. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
  124. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
  125. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
  126. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
  127. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
  128. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
  129. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
  130. package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
  131. package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
  132. package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
  133. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
  134. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  135. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  136. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
  137. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
  138. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
  139. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
  140. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
  141. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
  142. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
  143. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
  144. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
  145. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
  146. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
  147. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
  148. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
  149. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
  150. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
  151. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
  152. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
  153. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
  154. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
  155. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
  156. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
  157. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
@@ -123,7 +123,7 @@ test('repo object chunks travel through the shared tunnel one at a time', async
123
123
  }
124
124
  });
125
125
 
126
- test('repo object chunks retry after the tunnel reconnects', async () => {
126
+ test('repo object chunks retry when an acknowledgement is lost', async () => {
127
127
  const uploadBytes = Buffer.from('retry this content-addressed chunk');
128
128
  const uploadSha = crypto.createHash('sha256').update(uploadBytes).digest('hex');
129
129
  const resourceId = 'shr_cp1abcdefghijklmnopqrstuvwx';
@@ -165,25 +165,14 @@ test('repo object chunks retry after the tunnel reconnects', async () => {
165
165
  localPort: () => server.address().port,
166
166
  localHeaders: (headers) => headers,
167
167
  warn: () => {},
168
+ repoObjectRequestTimeoutMs: 25,
168
169
  });
169
170
 
170
171
  try {
171
- tunnel.enable(['shared.mutation.v1']);
172
- await until(
173
- () => frames.filter((frame) => frame.type === 'repo.object.put').length === 1,
174
- 'the original upload',
175
- );
176
- await new Promise((resolve) => { setTimeout(resolve, 50); });
177
- assert.equal(
178
- frames.filter((frame) => frame.type === 'repo.object.put').length,
179
- 1,
180
- 'a healthy ordered tunnel never duplicates an in-flight upload',
181
- );
182
- tunnel.stop();
183
172
  tunnel.enable(['shared.mutation.v1']);
184
173
  await until(
185
174
  () => frames.filter((frame) => frame.type === 'repo.object.put').length >= 2,
186
- 'the upload after reconnect',
175
+ 'the unacknowledged upload retry',
187
176
  );
188
177
  const puts = frames.filter((frame) => frame.type === 'repo.object.put');
189
178
  assert.notEqual(puts[0].request_id, puts[1].request_id);
@@ -200,160 +189,3 @@ test('repo object chunks retry after the tunnel reconnects', async () => {
200
189
  server.close();
201
190
  }
202
191
  });
203
-
204
- test('a rejected stale object yields the tunnel to the next upload', async () => {
205
- const staleBytes = Buffer.from('stale denied object');
206
- const staleSha = crypto.createHash('sha256').update(staleBytes).digest('hex');
207
- const currentBytes = Buffer.from('current allowed object');
208
- const currentSha = crypto.createHash('sha256').update(currentBytes).digest('hex');
209
- const resourceId = 'shr_cp1abcdefghijklmnopqrstuvwx';
210
- let failed = false;
211
- const calls = [];
212
- const server = http.createServer((req, res) => {
213
- let raw = '';
214
- req.on('data', (chunk) => { raw += chunk; });
215
- req.on('end', () => {
216
- const body = raw ? JSON.parse(raw) : null;
217
- calls.push({ path: req.url, body });
218
- res.setHeader('content-type', 'application/json');
219
- if (req.url === '/state/shared/replicas') {
220
- res.end('{"replicas":[]}');
221
- } else if (req.url.startsWith('/state/stream')) {
222
- res.writeHead(200, { 'content-type': 'text/event-stream' });
223
- res.write('\n');
224
- } else if (req.url === '/state/repo-objects/uploads/claim') {
225
- const bytes = failed ? currentBytes : staleBytes;
226
- const sha256 = failed ? currentSha : staleSha;
227
- res.end(JSON.stringify({
228
- upload: {
229
- resourceId,
230
- sha256,
231
- bytes: bytes.length,
232
- dataBase64: bytes.toString('base64'),
233
- },
234
- }));
235
- } else if (req.url === '/state/repo-objects/uploads/fail') {
236
- failed = true;
237
- res.end('{"changed":true,"terminal":true}');
238
- } else if (req.url.endsWith('/claim')) {
239
- res.end('{}');
240
- } else {
241
- res.end('{}');
242
- }
243
- });
244
- });
245
- await new Promise((resolve) => { server.listen(0, '127.0.0.1', resolve); });
246
-
247
- const frames = [];
248
- const tunnel = createSharedRealtimeTunnel({
249
- send: (frame) => { frames.push(frame); return true; },
250
- sendLarge: (frame) => { frames.push(frame); return true; },
251
- localPort: () => server.address().port,
252
- localHeaders: (headers) => headers,
253
- warn: () => {},
254
- });
255
-
256
- try {
257
- tunnel.enable(['shared.mutation.v1']);
258
- await until(
259
- () => frames.some((frame) => frame.type === 'repo.object.put'),
260
- 'the stale upload',
261
- );
262
- const stale = frames.find((frame) => frame.type === 'repo.object.put');
263
- assert.equal(stale.sha256, staleSha);
264
- tunnel.handleFrame({
265
- type: 'shared.error',
266
- request_id: stale.request_id,
267
- resource_id: resourceId,
268
- code: 'shared_resource_read_forbidden',
269
- message: 'stale rail is no longer readable',
270
- retryable: false,
271
- });
272
- await until(
273
- () => frames.some((frame) => (
274
- frame.type === 'repo.object.put' && frame.sha256 === currentSha
275
- )),
276
- 'the current upload behind the stale one',
277
- );
278
- assert.deepEqual(
279
- calls.find((call) => call.path === '/state/repo-objects/uploads/fail').body,
280
- {
281
- resourceId,
282
- sha256: staleSha,
283
- retryable: false,
284
- code: 'shared_resource_read_forbidden',
285
- message: 'stale rail is no longer readable',
286
- },
287
- );
288
- } finally {
289
- tunnel.stop();
290
- server.close();
291
- }
292
- });
293
-
294
- test('a healthy tunnel sends one request for one durable mutation', async () => {
295
- const envelope = {
296
- resourceId: '018f0000-0000-7000-8000-000000000050',
297
- authorityEpoch: 1,
298
- mutationId: 'mut_one_ordered_request',
299
- deviceId: 'machine-a',
300
- baseVersion: 0,
301
- contract: 'code-project-stream@2',
302
- schemaVersion: 2,
303
- operationKind: 'code.publish',
304
- operation: {
305
- kind: 'code.publish',
306
- project: '018f0000-0000-7000-8000-000000000050',
307
- delta: { kind: 'checkpoint.set' },
308
- },
309
- };
310
- let wakeResponse = null;
311
- const server = http.createServer((req, res) => {
312
- req.resume();
313
- req.on('end', () => {
314
- res.setHeader('content-type', 'application/json');
315
- if (req.url === '/state/shared/replicas') {
316
- res.end('{"replicas":[]}');
317
- } else if (req.url.startsWith('/state/stream')) {
318
- res.writeHead(200, { 'content-type': 'text/event-stream' });
319
- res.write('\n');
320
- wakeResponse = res;
321
- } else if (req.url === '/state/mutations/claim') {
322
- res.end(JSON.stringify({ mutations: [{ envelope }] }));
323
- } else if (req.url.endsWith('/claim')) {
324
- res.end('{}');
325
- } else {
326
- res.end('{}');
327
- }
328
- });
329
- });
330
- await new Promise((resolve) => { server.listen(0, '127.0.0.1', resolve); });
331
-
332
- const frames = [];
333
- const tunnel = createSharedRealtimeTunnel({
334
- send: (frame) => { frames.push(frame); return true; },
335
- sendLarge: (frame) => { frames.push(frame); return true; },
336
- localPort: () => server.address().port,
337
- localHeaders: (headers) => headers,
338
- warn: () => {},
339
- });
340
- const submits = () => frames.filter((frame) => frame.type === 'shared.mutation.submit');
341
-
342
- try {
343
- tunnel.enable(['shared.mutation.v1']);
344
- await until(() => submits().length === 1 && wakeResponse, 'the original mutation request');
345
- for (let index = 0; index < 3; index += 1) {
346
- wakeResponse.write('event: state\ndata: {"sharedResourceId":"shr_cp1abcdefghijklmnopqrstuvwx"}\n\n');
347
- await new Promise((resolve) => { setTimeout(resolve, 20); });
348
- }
349
- assert.equal(submits().length, 1, 'wake signals never duplicate an in-flight mutation');
350
-
351
- tunnel.stop();
352
- tunnel.enable(['shared.mutation.v1']);
353
- await until(() => submits().length === 2, 'the mutation after reconnect');
354
- assert.notEqual(submits()[0].request_id, submits()[1].request_id);
355
- } finally {
356
- tunnel.stop();
357
- server.close();
358
- }
359
- });
@@ -2,7 +2,6 @@
2
2
 
3
3
  const test = require('node:test');
4
4
  const assert = require('node:assert/strict');
5
- const crypto = require('crypto');
6
5
  const fs = require('fs');
7
6
  const os = require('os');
8
7
  const path = require('path');
@@ -26,38 +25,16 @@ fs.writeFileSync(
26
25
  const { closeLocalDb } = require('../state/db');
27
26
  const { hasCommit, headOid } = require('../workspace/git');
28
27
  const {
29
- _test: { locatorChecksum },
30
28
  acknowledgeUpload,
31
29
  claimDownload,
32
30
  claimUpload,
33
- failUpload,
34
31
  importFromLocator,
35
32
  objectSourceFor,
36
- prepareBundleImport,
37
33
  publishPayload,
38
34
  readPayload,
39
35
  receiveDownload,
40
36
  } = require('../workspace/objects');
41
37
 
42
- const PROJECT = '018f0000-0000-7000-8000-000000000030';
43
- const PROJECT_BINDING = require('../workspace/wall').bindProject(
44
- PROJECT,
45
- '11111111-1111-4111-8111-111111111111',
46
- tempRoot,
47
- );
48
- const REPO_KEY = PROJECT_BINDING.repoKey;
49
-
50
- test('a locator checksum binds the exact ordered chunk list', () => {
51
- const first = { sha256: 'a'.repeat(64), bytes: 3 };
52
- const second = { sha256: 'b'.repeat(64), bytes: 4 };
53
- assert.equal(locatorChecksum([first, second]), locatorChecksum([first, second]));
54
- assert.notEqual(locatorChecksum([first, second]), locatorChecksum([second, first]));
55
- assert.notEqual(
56
- locatorChecksum([first, second]),
57
- locatorChecksum([first, { ...second, bytes: 5 }]),
58
- );
59
- });
60
-
61
38
  test.after(() => {
62
39
  closeLocalDb();
63
40
  fs.rmSync(tempRoot, { recursive: true, force: true });
@@ -72,7 +49,7 @@ async function git(cwd, ...args) {
72
49
  return stdout;
73
50
  }
74
51
 
75
- test('every committed card travels as project-owned chunks and imports without moving a ref', async () => {
52
+ test('an unpushed commit travels as card-owned chunks and imports without moving a ref', async () => {
76
53
  const origin = path.join(tempRoot, 'origin.git');
77
54
  await git(tempRoot, 'init', '--bare', '-b', 'main', origin);
78
55
  const source = path.join(tempRoot, 'source');
@@ -84,12 +61,12 @@ test('every committed card travels as project-owned chunks and imports without m
84
61
 
85
62
  const destination = path.join(tempRoot, 'destination');
86
63
  await git(tempRoot, 'clone', '-q', origin, destination);
87
- assert.equal((await objectSourceFor(source, REPO_KEY, await headOid(source))).kind, 'pending');
64
+ assert.equal((await objectSourceFor(source, 'repo:test', await headOid(source))).kind, 'remote');
88
65
 
89
66
  fs.appendFileSync(path.join(source, 'app.js'), 'two\n');
90
67
  await git(source, 'commit', '-am', 'two');
91
68
  const unpushedOid = await headOid(source);
92
- assert.equal((await objectSourceFor(source, REPO_KEY, unpushedOid)).kind, 'pending');
69
+ assert.equal((await objectSourceFor(source, 'repo:test', unpushedOid)).kind, 'pending');
93
70
 
94
71
  const uploaded = new Map();
95
72
  const deadline = Date.now() + 10_000;
@@ -101,10 +78,10 @@ test('every committed card travels as project-owned chunks and imports without m
101
78
  }
102
79
  uploaded.set(upload.sha256, upload.dataBase64);
103
80
  acknowledgeUpload(upload.resourceId, upload.sha256);
104
- const sourceState = await objectSourceFor(source, REPO_KEY, unpushedOid);
81
+ const sourceState = await objectSourceFor(source, 'repo:test', unpushedOid);
105
82
  if (sourceState.kind === 'bundle') break;
106
83
  }
107
- const locator = await objectSourceFor(source, REPO_KEY, unpushedOid);
84
+ const locator = await objectSourceFor(source, 'repo:test', unpushedOid);
108
85
  assert.equal(locator.kind, 'bundle');
109
86
  assert.ok(locator.chunks.length > 0);
110
87
 
@@ -126,7 +103,7 @@ test('every committed card travels as project-owned chunks and imports without m
126
103
 
127
104
  test('a large checkpoint uses the same verified chunk lane', () => {
128
105
  const content = Buffer.alloc(1_300_000, 0x61);
129
- assert.equal(publishPayload('large-checkpoint', content, REPO_KEY).kind, 'pending');
106
+ assert.equal(publishPayload('large-checkpoint', content, 'repo:test').kind, 'pending');
130
107
 
131
108
  const uploaded = new Map();
132
109
  while (true) {
@@ -135,7 +112,7 @@ test('a large checkpoint uses the same verified chunk lane', () => {
135
112
  uploaded.set(upload.sha256, upload.dataBase64);
136
113
  acknowledgeUpload(upload.resourceId, upload.sha256);
137
114
  }
138
- const locator = publishPayload('large-checkpoint', content, REPO_KEY);
115
+ const locator = publishPayload('large-checkpoint', content, 'repo:test');
139
116
  assert.equal(locator.kind, 'chunks');
140
117
  assert.ok(locator.chunks.length >= 3);
141
118
 
@@ -152,194 +129,4 @@ test('a large checkpoint uses the same verified chunk lane', () => {
152
129
  const received = readPayload(locator);
153
130
  assert.equal(received.state, 'ready');
154
131
  assert.deepEqual(received.bytes, content);
155
- assert.equal(
156
- publishPayload('same-content-after-download', content, REPO_KEY).kind,
157
- 'chunks',
158
- 'verified downloaded content is immediately reusable by a later checkpoint',
159
- );
160
- assert.equal(claimUpload(), null);
161
- });
162
-
163
- test('zero-byte chunks are not legal transport work', () => {
164
- const chunks = [
165
- { sha256: '0'.repeat(64), bytes: 0 },
166
- { sha256: '1'.repeat(64), bytes: 1 },
167
- ];
168
- const common = {
169
- resourceId: PROJECT,
170
- checksum: locatorChecksum(chunks),
171
- bytes: 1,
172
- chunks,
173
- };
174
- assert.throws(
175
- () => readPayload({
176
- kind: 'chunks',
177
- contract: 'opaque-chunks@2',
178
- ...common,
179
- }),
180
- /invalid checkpoint payload locator/,
181
- );
182
- assert.throws(
183
- () => prepareBundleImport({
184
- kind: 'bundle',
185
- contract: 'git-bundle-chunks@2',
186
- oid: 'a'.repeat(40),
187
- ref: `refs/amalgm/exports/${'a'.repeat(40)}`,
188
- ...common,
189
- }),
190
- /invalid git bundle locator/,
191
- );
192
- });
193
-
194
- test('a corrupt pending upload is retired and rebuilt from the writer', () => {
195
- const content = Buffer.from('rebuild this upload');
196
- assert.equal(publishPayload('corrupt-upload', content, REPO_KEY).kind, 'pending');
197
- const first = claimUpload();
198
- assert.ok(first);
199
- const chunkFile = path.join(
200
- process.env.AMALGM_DIR,
201
- 'state',
202
- 'repo-objects',
203
- 'chunks',
204
- `${first.sha256}.bin`,
205
- );
206
- fs.writeFileSync(chunkFile, 'corrupt');
207
- assert.equal(claimUpload(), null, 'the corrupt manifest is retired instead of retrying forever');
208
-
209
- assert.equal(publishPayload('corrupt-upload', content, REPO_KEY).kind, 'pending');
210
- const rebuilt = claimUpload();
211
- assert.equal(rebuilt.sha256, first.sha256);
212
- assert.equal(Buffer.from(rebuilt.dataBase64, 'base64').toString(), content.toString());
213
- acknowledgeUpload(rebuilt.resourceId, rebuilt.sha256);
214
- });
215
-
216
- test('one malformed download request cannot block valid requests behind it', () => {
217
- const content = Buffer.from('download me');
218
- const chunks = [{
219
- sha256: require('crypto').createHash('sha256').update(content).digest('hex'),
220
- bytes: content.length,
221
- }];
222
- const locator = {
223
- kind: 'chunks',
224
- contract: 'opaque-chunks@2',
225
- resourceId: PROJECT,
226
- checksum: locatorChecksum(chunks),
227
- bytes: content.length,
228
- chunks,
229
- };
230
- fs.rmSync(path.join(
231
- process.env.AMALGM_DIR,
232
- 'state',
233
- 'repo-objects',
234
- 'chunks',
235
- `${chunks[0].sha256}.bin`,
236
- ), { force: true });
237
- assert.equal(readPayload(locator).state, 'downloading');
238
- const requests = path.join(process.env.AMALGM_DIR, 'state', 'repo-objects', 'requests');
239
- fs.writeFileSync(path.join(requests, '000-malformed.json'), '{');
240
-
241
- const claimed = claimDownload();
242
- assert.equal(claimed.resourceId, PROJECT);
243
- assert.equal(claimed.sha256, chunks[0].sha256);
244
- assert.equal(fs.existsSync(path.join(requests, '000-malformed.json')), false);
245
- receiveDownload(PROJECT, chunks[0].sha256, content.toString('base64'));
246
- });
247
-
248
- test('failed uploads yield the bulk lane to later durable objects', () => {
249
- for (const content of ['first queued object', 'second queued object', 'third queued object']) {
250
- assert.equal(
251
- publishPayload(`fair-${content}`, Buffer.from(content), REPO_KEY).kind,
252
- 'pending',
253
- );
254
- }
255
-
256
- const first = claimUpload();
257
- assert.ok(first);
258
- const retry = failUpload(first.resourceId, first.sha256, {
259
- retryable: true,
260
- code: 'temporary_object_failure',
261
- message: 'try this object later',
262
- });
263
- assert.equal(retry.changed, true);
264
- assert.equal(retry.terminal, false);
265
- assert.ok(Date.parse(retry.retryAfter) > Date.now());
266
-
267
- const second = claimUpload();
268
- assert.ok(second);
269
- assert.notEqual(second.sha256, first.sha256);
270
- const terminal = failUpload(second.resourceId, second.sha256, {
271
- retryable: false,
272
- code: 'invalid_repo_object',
273
- message: 'this exact object is invalid',
274
- });
275
- assert.equal(terminal.changed, true);
276
- assert.equal(terminal.terminal, true);
277
-
278
- const third = claimUpload();
279
- assert.ok(third);
280
- assert.notEqual(third.sha256, first.sha256);
281
- assert.notEqual(third.sha256, second.sha256);
282
-
283
- acknowledgeUpload(first.resourceId, first.sha256);
284
- acknowledgeUpload(second.resourceId, second.sha256);
285
- acknowledgeUpload(third.resourceId, third.sha256);
286
- assert.equal(claimUpload(), null);
287
- });
288
-
289
- test('legacy shared-resource uploads can terminate without starving current projects', () => {
290
- const legacyResourceId = 'shr_legacyobjectqueue0001';
291
- const legacyChunks = [
292
- Buffer.from('first stale object chunk from a legacy rail'),
293
- Buffer.from('second stale object chunk from a legacy rail'),
294
- ].map((bytes) => ({
295
- bytes,
296
- sha256: crypto.createHash('sha256').update(bytes).digest('hex'),
297
- }));
298
- const objectRoot = path.join(process.env.AMALGM_DIR, 'state', 'repo-objects');
299
- const manifests = path.join(objectRoot, 'manifests');
300
- const chunks = path.join(objectRoot, 'chunks');
301
- for (const chunk of legacyChunks) {
302
- fs.writeFileSync(path.join(chunks, `${chunk.sha256}.bin`), chunk.bytes);
303
- }
304
- fs.writeFileSync(path.join(manifests, '000-legacy-shared-resource.json'), JSON.stringify({
305
- contract: 'git-bundle-chunks@2',
306
- resourceId: legacyResourceId,
307
- repoKey: 'legacy-repo',
308
- oid: '1'.repeat(40),
309
- ref: `refs/amalgm/exports/${'1'.repeat(40)}`,
310
- checksum: locatorChecksum(legacyChunks.map((chunk) => ({
311
- sha256: chunk.sha256,
312
- bytes: chunk.bytes.length,
313
- }))),
314
- bytes: legacyChunks.reduce((total, chunk) => total + chunk.bytes.length, 0),
315
- chunks: legacyChunks.map((chunk) => ({
316
- sha256: chunk.sha256,
317
- bytes: chunk.bytes.length,
318
- uploaded: false,
319
- })),
320
- createdAt: new Date().toISOString(),
321
- }));
322
-
323
- assert.equal(
324
- publishPayload('current-after-legacy', Buffer.from('current project object'), REPO_KEY).kind,
325
- 'pending',
326
- );
327
- const legacy = claimUpload();
328
- assert.equal(legacy.resourceId, legacyResourceId);
329
- assert.equal(legacy.sha256, legacyChunks[0].sha256);
330
- const terminal = failUpload(legacy.resourceId, legacy.sha256, {
331
- retryable: false,
332
- code: 'shared_resource_read_forbidden',
333
- message: 'the legacy rail is no longer readable',
334
- });
335
- assert.equal(terminal.changed, true);
336
- assert.equal(terminal.terminal, true);
337
-
338
- const current = claimUpload();
339
- assert.equal(current.resourceId, PROJECT);
340
- assert.ok(
341
- legacyChunks.every((chunk) => current.sha256 !== chunk.sha256),
342
- 'a resource-wide rejection skips every remaining chunk from the stale rail',
343
- );
344
- acknowledgeUpload(current.resourceId, current.sha256);
345
132
  });