amalgm 0.1.239 → 0.1.241

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 (87) hide show
  1. package/lib/shared-realtime-tunnel.js +18 -4
  2. package/package.json +1 -1
  3. package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +68 -20
  4. package/runtime/scripts/amalgm-mcp/project-context/store.js +87 -19
  5. package/runtime/scripts/amalgm-mcp/server/routes/state.js +13 -0
  6. package/runtime/scripts/amalgm-mcp/state/attachments.js +40 -12
  7. package/runtime/scripts/amalgm-mcp/state/db.js +216 -17
  8. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +550 -0
  9. package/runtime/scripts/amalgm-mcp/state/docs.js +1101 -301
  10. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +99 -0
  11. package/runtime/scripts/amalgm-mcp/state/mutations.js +507 -130
  12. package/runtime/scripts/amalgm-mcp/state/promotions.js +21 -1
  13. package/runtime/scripts/amalgm-mcp/state/replicas.js +134 -13
  14. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +14 -8
  15. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +193 -0
  16. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +313 -94
  17. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +1 -0
  18. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +32 -17
  19. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +84 -0
  20. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +376 -13
  21. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +309 -0
  22. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +758 -30
  23. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +72 -3
  24. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +33 -6
  25. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +27 -0
  26. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +334 -191
  27. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +24 -0
  28. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +1999 -0
  29. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +364 -0
  30. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +98 -4
  31. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +156 -8
  32. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +185 -0
  33. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +174 -269
  34. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +26 -0
  35. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +2060 -407
  36. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +119 -0
  37. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +1446 -0
  38. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +5319 -3
  39. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +191 -1
  40. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +3140 -17
  41. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +135 -637
  42. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +5 -1
  43. package/runtime/scripts/amalgm-mcp/workspace/card.js +7 -7
  44. package/runtime/scripts/amalgm-mcp/workspace/cards.js +36 -12
  45. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +436 -173
  46. package/runtime/scripts/amalgm-mcp/workspace/content.js +32 -0
  47. package/runtime/scripts/amalgm-mcp/workspace/durable.js +45 -0
  48. package/runtime/scripts/amalgm-mcp/workspace/git.js +391 -37
  49. package/runtime/scripts/amalgm-mcp/workspace/intent.js +535 -0
  50. package/runtime/scripts/amalgm-mcp/workspace/merge.js +89 -6
  51. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +31 -0
  52. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  53. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  54. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +72 -0
  55. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +165 -0
  56. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +5 -0
  57. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +2 -0
  58. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +2201 -0
  59. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +19 -0
  60. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +18 -0
  61. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +18 -0
  62. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +33 -0
  63. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +47 -0
  64. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +43 -0
  65. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +114 -0
  66. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +84 -0
  67. package/runtime/scripts/amalgm-mcp/workspace/objects.js +185 -55
  68. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +616 -168
  69. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +240 -0
  70. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +1979 -570
  71. package/runtime/scripts/amalgm-mcp/workspace/rest.js +49 -28
  72. package/runtime/scripts/amalgm-mcp/workspace/store.js +77 -4
  73. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +240 -0
  74. package/runtime/scripts/amalgm-mcp/workspace/transition.js +1729 -192
  75. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +100 -0
  76. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +7 -1
  77. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +73 -61
  78. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +127 -0
  79. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +12 -0
  80. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +51 -6
  81. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +125 -0
  82. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +79 -8
  83. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +76 -0
  84. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +3480 -165
  85. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +3766 -0
  86. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +899 -71
  87. package/runtime/scripts/amalgm-mcp/workspace/wall.js +352 -497
@@ -5,7 +5,7 @@ const assert = require('node:assert/strict');
5
5
  const fs = require('fs');
6
6
  const os = require('os');
7
7
  const path = require('path');
8
- const { execFile } = require('child_process');
8
+ const { execFile, spawn } = require('child_process');
9
9
  const { promisify } = require('util');
10
10
 
11
11
  const execFileAsync = promisify(execFile);
@@ -29,7 +29,7 @@ const {
29
29
  captureWorktreeDiff,
30
30
  } = require('../workspace/git');
31
31
  const merge = require('../workspace/merge');
32
- const { applyPatch } = require('../workspace/transition');
32
+ const { advanceIndex } = require('../workspace/reducer');
33
33
  const {
34
34
  _test,
35
35
  CHECKPOINT_CONTRACT,
@@ -80,6 +80,58 @@ test('a checkpoint read across two different HEADs is discarded', () => {
80
80
  });
81
81
  });
82
82
 
83
+ test('a checkpoint read across two different index or worktree states is discarded', () => {
84
+ const clean = {
85
+ baseOid: 'a'.repeat(40),
86
+ indexDiff: '',
87
+ worktreeDiff: '',
88
+ bytes: 0,
89
+ };
90
+ const staged = {
91
+ ...clean,
92
+ indexDiff: 'staged bytes',
93
+ bytes: 12,
94
+ };
95
+ assert.deepEqual(gitTest.stableCheckpointCapture(clean, staged), {
96
+ baseOid: 'a'.repeat(40),
97
+ indexDiff: null,
98
+ worktreeDiff: null,
99
+ reason: 'observation-moved',
100
+ bytes: 12,
101
+ });
102
+ assert.deepEqual(gitTest.stableCheckpointCapture(staged, staged), staged);
103
+ });
104
+
105
+ test('checkpoint capture requires two consecutive equal observations within its bound', async () => {
106
+ const clean = {
107
+ baseOid: 'a'.repeat(40),
108
+ indexDiff: '',
109
+ worktreeDiff: '',
110
+ bytes: 0,
111
+ };
112
+ const staged = {
113
+ ...clean,
114
+ indexDiff: 'staged bytes',
115
+ bytes: 12,
116
+ };
117
+ const settling = [clean, staged, staged];
118
+ assert.deepEqual(
119
+ await gitTest.captureStableCheckpoint(() => Promise.resolve(settling.shift()), 3),
120
+ staged,
121
+ );
122
+ const alternating = [clean, staged, clean, staged];
123
+ assert.deepEqual(
124
+ await gitTest.captureStableCheckpoint(() => Promise.resolve(alternating.shift()), 4),
125
+ {
126
+ baseOid: 'a'.repeat(40),
127
+ indexDiff: null,
128
+ worktreeDiff: null,
129
+ reason: 'observation-moved',
130
+ bytes: 12,
131
+ },
132
+ );
133
+ });
134
+
83
135
  async function run(cwd, ...args) {
84
136
  await execFileAsync(
85
137
  'git',
@@ -114,7 +166,7 @@ async function makeRepoPair() {
114
166
  return { author, receiver };
115
167
  }
116
168
 
117
- test('a dirty worktree round-trips through capture and apply, gitignore filtering included', async () => {
169
+ test('a dirty worktree round-trips through checkpoint trees, gitignore filtering included', async () => {
118
170
  const { author, receiver } = await makeRepoPair();
119
171
 
120
172
  // Every dirty shape at once: tracked edit, staged edit, deletion, new
@@ -129,13 +181,32 @@ test('a dirty worktree round-trips through capture and apply, gitignore filterin
129
181
  fs.mkdirSync(path.join(author, 'node_modules', 'junk'), { recursive: true });
130
182
  fs.writeFileSync(path.join(author, 'node_modules', 'junk', 'x.js'), 'never travels\n');
131
183
 
132
- const captured = await captureWorktreeDiff(author);
133
- assert.ok(captured.diff.length > 0);
134
- assert.ok(!captured.diff.includes('node_modules'), 'ignored files must never enter a checkpoint');
135
-
136
- const patchFile = path.join(tempRoot, 'roundtrip.patch');
137
- fs.writeFileSync(patchFile, captured.diff);
138
- await applyPatch(receiver, patchFile);
184
+ const captured = await captureCheckpoint(author);
185
+ assert.ok(captured.worktreeDiff.length > 0);
186
+ assert.ok(
187
+ !captured.worktreeDiff.includes('node_modules'),
188
+ 'ignored files must never enter a checkpoint',
189
+ );
190
+ const baseTree = await merge.treeOf(receiver, captured.baseOid);
191
+ const indexTree = await merge.treeOfPatch(receiver, captured.baseOid, captured.indexDiff);
192
+ const worktreeTree = await merge.treeOfPatch(
193
+ receiver,
194
+ indexTree,
195
+ captured.worktreeDiff,
196
+ );
197
+ await merge.advance(receiver, baseTree, worktreeTree, { label: 'checkpoint-test-forward' });
198
+ const forwardIndexOperation = '1'.repeat(64);
199
+ await advanceIndex(
200
+ receiver,
201
+ await merge.indexTree(receiver),
202
+ indexTree,
203
+ forwardIndexOperation,
204
+ );
205
+ require('../workspace/transition').releaseGitMetadataReceipts(
206
+ receiver,
207
+ forwardIndexOperation,
208
+ 'index',
209
+ );
139
210
 
140
211
  assert.equal(
141
212
  fs.readFileSync(path.join(receiver, 'README.md'), 'utf8'),
@@ -156,8 +227,20 @@ test('a dirty worktree round-trips through capture and apply, gitignore filterin
156
227
  assert.equal(fs.existsSync(path.join(receiver, 'LICENSE')), false, 'deletions travel');
157
228
  assert.equal(fs.existsSync(path.join(receiver, 'node_modules')), false);
158
229
 
159
- // And the reverse of the same patch restores the receiver to clean.
160
- await applyPatch(receiver, patchFile, { reverse: true });
230
+ // The same two primitives can advance back to the clean shared state.
231
+ await merge.advance(receiver, worktreeTree, baseTree, { label: 'checkpoint-test-reverse' });
232
+ const reverseIndexOperation = '2'.repeat(64);
233
+ await advanceIndex(
234
+ receiver,
235
+ await merge.indexTree(receiver),
236
+ baseTree,
237
+ reverseIndexOperation,
238
+ );
239
+ require('../workspace/transition').releaseGitMetadataReceipts(
240
+ receiver,
241
+ reverseIndexOperation,
242
+ 'index',
243
+ );
161
244
  const after = await captureWorktreeDiff(receiver);
162
245
  assert.equal(after.diff, '');
163
246
  });
@@ -171,6 +254,215 @@ test('capture is stable: two reads of an untouched tree produce the same revisio
171
254
  assert.equal(revisionOf(first.baseOid, first.diff), revisionOf(second.baseOid, second.diff));
172
255
  });
173
256
 
257
+ test('index ABA cannot produce a checkpoint that never physically existed', async () => {
258
+ const author = path.join(tempRoot, 'index-aba-author');
259
+ fs.mkdirSync(author);
260
+ await run(author, 'init');
261
+ fs.writeFileSync(path.join(author, 'file.txt'), 'floor\n');
262
+ await run(author, 'add', 'file.txt');
263
+ await run(author, 'commit', '-m', 'floor');
264
+ fs.writeFileSync(path.join(author, 'file.txt'), 'local\n');
265
+
266
+ const mover = spawn('/bin/sh', ['-c', `
267
+ index=0
268
+ while [ "$index" -lt 500 ]; do
269
+ git reset -q HEAD -- file.txt
270
+ git add file.txt
271
+ index=$((index + 1))
272
+ done
273
+ `], {
274
+ cwd: author,
275
+ env: { ...process.env, GIT_CONFIG_NOSYSTEM: '1', GIT_TERMINAL_PROMPT: '0' },
276
+ stdio: ['ignore', 'ignore', 'pipe'],
277
+ });
278
+ let moved = 0;
279
+ try {
280
+ for (let attempt = 0; attempt < 8; attempt += 1) {
281
+ const captured = await captureCheckpoint(author);
282
+ if (captured.reason === 'observation-moved') {
283
+ moved += 1;
284
+ continue;
285
+ }
286
+ assert.ok(
287
+ captured.indexDiff.length > 0 || captured.worktreeDiff.length > 0,
288
+ 'capture returned an impossible clean index + clean worktree mixture',
289
+ );
290
+ }
291
+ } finally {
292
+ mover.kill('SIGTERM');
293
+ await new Promise((resolve) => {
294
+ if (mover.exitCode !== null) resolve();
295
+ else mover.once('exit', resolve);
296
+ });
297
+ }
298
+ assert.ok(moved > 0, 'the test did not overlap an index movement');
299
+ });
300
+
301
+ test('directory membership ABA cannot hide an untracked file from capture', async () => {
302
+ const author = path.join(tempRoot, 'membership-aba-author');
303
+ fs.mkdirSync(author);
304
+ await run(author, 'init', '-b', 'main');
305
+ fs.writeFileSync(path.join(author, 'tracked.txt'), 'floor\n');
306
+ await run(author, 'add', 'tracked.txt');
307
+ await run(author, 'commit', '-m', 'floor');
308
+
309
+ const source = String.raw`
310
+ const childProcess = require('child_process');
311
+ const fs = require('fs');
312
+ const path = require('path');
313
+ const { promisify } = require('util');
314
+ const originalExecFile = childProcess.execFile;
315
+ const late = path.join(process.env.TEST_REPO, 'late.txt');
316
+ let otherCalls = 0;
317
+ const wrappedExecFile = function(file, args, options, callback) {
318
+ const isUntracked = file === 'git'
319
+ && Array.isArray(args)
320
+ && args.includes('ls-files')
321
+ && args.includes('--others');
322
+ if (!isUntracked) return originalExecFile(file, args, options, callback);
323
+ otherCalls += 1;
324
+ fs.rmSync(late, { force: true });
325
+ return originalExecFile(file, args, options, (error, stdout, stderr) => {
326
+ fs.writeFileSync(late, 'late bytes\n');
327
+ callback(error, stdout, stderr);
328
+ });
329
+ };
330
+ wrappedExecFile[promisify.custom] = (file, args, options) => new Promise(
331
+ (resolve, reject) => wrappedExecFile(file, args, options, (error, stdout, stderr) => {
332
+ if (error) {
333
+ error.stdout = stdout;
334
+ error.stderr = stderr;
335
+ reject(error);
336
+ } else {
337
+ resolve({ stdout, stderr });
338
+ }
339
+ }),
340
+ );
341
+ childProcess.execFile = wrappedExecFile;
342
+ const { captureCheckpoint } = require(process.env.GIT_MODULE);
343
+ captureCheckpoint(process.env.TEST_REPO).then((captured) => {
344
+ process.stdout.write(JSON.stringify({
345
+ captured,
346
+ otherCalls,
347
+ finalLateExists: fs.existsSync(late),
348
+ }));
349
+ }).catch((error) => {
350
+ process.stderr.write(error.stack || String(error));
351
+ process.exitCode = 1;
352
+ });
353
+ `;
354
+ const { stdout } = await execFileAsync(process.execPath, ['-e', source], {
355
+ env: {
356
+ ...process.env,
357
+ TEST_REPO: author,
358
+ GIT_MODULE: require.resolve('../workspace/git'),
359
+ },
360
+ timeout: 30_000,
361
+ maxBuffer: 8 * 1024 * 1024,
362
+ });
363
+ const result = JSON.parse(stdout);
364
+ assert.equal(result.finalLateExists, true);
365
+ assert.ok(result.otherCalls >= 4);
366
+ assert.equal(result.captured.reason, 'observation-moved');
367
+ assert.equal(result.captured.indexDiff, null);
368
+ assert.equal(result.captured.worktreeDiff, null);
369
+ });
370
+
371
+ test('same-inode directory rename ABA cannot hide its untracked children', async () => {
372
+ const author = path.join(tempRoot, 'directory-rename-aba-author');
373
+ fs.mkdirSync(author);
374
+ await run(author, 'init', '-b', 'main');
375
+ fs.writeFileSync(path.join(author, 'tracked.txt'), 'floor\n');
376
+ await run(author, 'add', 'tracked.txt');
377
+ await run(author, 'commit', '-m', 'floor');
378
+ fs.mkdirSync(path.join(author, 'local'));
379
+ fs.writeFileSync(path.join(author, 'local', 'local.txt'), 'local bytes\n');
380
+
381
+ const source = String.raw`
382
+ const childProcess = require('child_process');
383
+ const fs = require('fs');
384
+ const path = require('path');
385
+ const { promisify } = require('util');
386
+ const originalExecFile = childProcess.execFile;
387
+ const held = path.join(process.env.TEST_REPO, 'local');
388
+ const outside = path.join(path.dirname(process.env.TEST_REPO), 'held-local');
389
+ const originalIno = fs.statSync(held).ino;
390
+ let intercepted = 0;
391
+ const wrappedExecFile = function(file, args, options, callback) {
392
+ const isUntracked = file === 'git'
393
+ && Array.isArray(args)
394
+ && args.includes('ls-files')
395
+ && args.includes('--others')
396
+ && !args.includes('--ignored');
397
+ if (!isUntracked) return originalExecFile(file, args, options, callback);
398
+ intercepted += 1;
399
+ fs.renameSync(held, outside);
400
+ return originalExecFile(file, args, options, (error, stdout, stderr) => {
401
+ fs.renameSync(outside, held);
402
+ callback(error, stdout, stderr);
403
+ });
404
+ };
405
+ wrappedExecFile[promisify.custom] = (file, args, options) => new Promise(
406
+ (resolve, reject) => wrappedExecFile(file, args, options, (error, stdout, stderr) => {
407
+ if (error) {
408
+ error.stdout = stdout;
409
+ error.stderr = stderr;
410
+ reject(error);
411
+ } else {
412
+ resolve({ stdout, stderr });
413
+ }
414
+ }),
415
+ );
416
+ childProcess.execFile = wrappedExecFile;
417
+ const { captureCheckpoint } = require(process.env.GIT_MODULE);
418
+ captureCheckpoint(process.env.TEST_REPO).then((captured) => {
419
+ process.stdout.write(JSON.stringify({
420
+ captured,
421
+ intercepted,
422
+ finalExists: fs.existsSync(path.join(held, 'local.txt')),
423
+ sameInode: fs.statSync(held).ino === originalIno,
424
+ }));
425
+ }).catch((error) => {
426
+ process.stderr.write(error.stack || String(error));
427
+ process.exitCode = 1;
428
+ });
429
+ `;
430
+ const { stdout } = await execFileAsync(process.execPath, ['-e', source], {
431
+ env: {
432
+ ...process.env,
433
+ TEST_REPO: author,
434
+ GIT_MODULE: require.resolve('../workspace/git'),
435
+ },
436
+ timeout: 30_000,
437
+ maxBuffer: 8 * 1024 * 1024,
438
+ });
439
+ const result = JSON.parse(stdout);
440
+ assert.ok(result.intercepted >= 2);
441
+ assert.equal(result.finalExists, true);
442
+ assert.equal(result.sameInode, true);
443
+ assert.equal(result.captured.reason, 'observation-moved');
444
+ });
445
+
446
+ test('an untracked nested repository is rejected instead of certified clean', async () => {
447
+ const author = path.join(tempRoot, 'nested-repository-author');
448
+ fs.mkdirSync(author);
449
+ await run(author, 'init', '-b', 'main');
450
+ fs.writeFileSync(path.join(author, 'tracked.txt'), 'floor\n');
451
+ await run(author, 'add', 'tracked.txt');
452
+ await run(author, 'commit', '-m', 'floor');
453
+ const nested = path.join(author, 'nested');
454
+ fs.mkdirSync(nested);
455
+ await run(nested, 'init', '-b', 'main');
456
+ fs.writeFileSync(path.join(nested, 'local.txt'), 'nested local bytes\n');
457
+ await run(nested, 'add', 'local.txt');
458
+ await run(nested, 'commit', '-m', 'nested');
459
+
460
+ const captured = await captureCheckpoint(author);
461
+ assert.equal(captured.reason, 'unsupported-nested-repository');
462
+ assert.equal(captured.indexDiff, null);
463
+ assert.equal(captured.worktreeDiff, null);
464
+ });
465
+
174
466
  test('checkpoint identity ignores Git file-patch order', async () => {
175
467
  const { author } = await makeRepoPair();
176
468
  fs.appendFileSync(path.join(author, 'README.md'), 'tracked edit\n');
@@ -196,10 +488,11 @@ test('one checkpoint preserves the staged and unstaged layers separately', async
196
488
  fs.appendFileSync(path.join(author, 'README.md'), 'unstaged\n');
197
489
 
198
490
  const captured = await captureCheckpoint(author);
199
- assert.ok(captured.indexDiff.includes('console.log(2)'));
200
- assert.ok(!captured.indexDiff.includes('console.log(3)'));
201
- assert.ok(captured.worktreeDiff.includes('console.log(3)'));
202
- assert.ok(captured.worktreeDiff.includes('unstaged'));
491
+ assert.equal(captured.reason, undefined, JSON.stringify(captured));
492
+ assert.ok(captured.indexDiff.includes('console.log(2)'), JSON.stringify(captured));
493
+ assert.ok(!captured.indexDiff.includes('console.log(3)'), JSON.stringify(captured));
494
+ assert.ok(captured.worktreeDiff.includes('console.log(3)'), JSON.stringify(captured));
495
+ assert.ok(captured.worktreeDiff.includes('unstaged'), JSON.stringify(captured));
203
496
 
204
497
  const indexTree = await merge.treeOfPatch(author, captured.baseOid, captured.indexDiff);
205
498
  const worktreeTree = await merge.treeOfPatch(author, indexTree, captured.worktreeDiff);
@@ -207,6 +500,23 @@ test('one checkpoint preserves the staged and unstaged layers separately', async
207
500
  assert.equal(await runOutput(author, 'show', `${worktreeTree}:src/index.js`), 'console.log(3);\n');
208
501
  });
209
502
 
503
+ test('same-size same-time saves cannot make Git mix staged identity with working bytes', async () => {
504
+ const { author } = await makeRepoPair();
505
+ await run(author, 'config', 'core.trustctime', 'false');
506
+ const file = path.join(author, 'src', 'index.js');
507
+ fs.writeFileSync(file, 'console.log(2);\n');
508
+ await run(author, 'add', 'src/index.js');
509
+ const stagedTime = fs.statSync(file);
510
+ fs.writeFileSync(file, 'console.log(3);\n');
511
+ fs.utimesSync(file, stagedTime.atime, stagedTime.mtime);
512
+
513
+ const captured = await captureCheckpoint(author);
514
+ assert.equal(captured.reason, undefined, JSON.stringify(captured));
515
+ assert.ok(captured.indexDiff.includes('console.log(2)'), JSON.stringify(captured));
516
+ assert.ok(!captured.indexDiff.includes('console.log(3)'), JSON.stringify(captured));
517
+ assert.ok(captured.worktreeDiff.includes('console.log(3)'), JSON.stringify(captured));
518
+ });
519
+
210
520
  test('a clean tree captures as an empty patch, and oversized state is a named refusal', async () => {
211
521
  const { author } = await makeRepoPair();
212
522
  const clean = await captureWorktreeDiff(author);
@@ -223,43 +533,7 @@ test('checkpoint captures are local cache; their operations travel on the one wa
223
533
  assert.equal(first, checkpointFile('m1', 'https://github.com/a/b'));
224
534
  assert.notEqual(first, checkpointFile('m2', 'https://github.com/a/b'));
225
535
  assert.notEqual(first, checkpointFile('m1', 'https://github.com/a/c'));
226
- assert.match(first, /repo-checkpoints-v9/);
227
- });
228
-
229
- test('a materialized peer card is immediately a valid checkpoint base', () => {
230
- const oid = 'a'.repeat(40);
231
- assert.equal(_test.checkpointBaseReady({
232
- head: { branch: 'main', oid },
233
- }, {
234
- appliedThrough: 12,
235
- branch: 'main',
236
- baseOid: oid,
237
- indexDiff: '',
238
- worktreeDiff: '',
239
- }, {
240
- receivedThrough: 12,
241
- appliedThrough: 12,
242
- pending: 0,
243
- current: true,
244
- }), true);
245
- });
246
-
247
- test('checkpoint publication waits when the reducer is behind the rail', () => {
248
- const oid = 'a'.repeat(40);
249
- const card = { head: { branch: 'main', oid } };
250
- const reduced = {
251
- appliedThrough: 11,
252
- branch: 'main',
253
- baseOid: oid,
254
- indexDiff: '',
255
- worktreeDiff: '',
256
- };
257
- assert.equal(_test.checkpointBaseReady(card, reduced, {
258
- receivedThrough: 12,
259
- appliedThrough: 11,
260
- pending: 0,
261
- current: false,
262
- }), false);
536
+ assert.match(first, /repo-checkpoints-v11/);
263
537
  });
264
538
 
265
539
  test('an uploading checkpoint payload is not published as a transition', () => {
@@ -267,7 +541,7 @@ test('an uploading checkpoint payload is not published as a transition', () => {
267
541
  revision: 'a'.repeat(64),
268
542
  payload: {
269
543
  kind: 'pending',
270
- contract: 'opaque-chunks@1',
544
+ contract: 'opaque-chunks@2',
271
545
  checksum: 'b'.repeat(64),
272
546
  },
273
547
  };
@@ -277,7 +551,12 @@ test('an uploading checkpoint payload is not published as a transition', () => {
277
551
  });
278
552
 
279
553
  test('a small edit after a large file reuses the large file payload', () => {
280
- const repoKey = 'https://example.test/granular-checkpoint.git';
554
+ const project = require('../workspace/wall').bindProject(
555
+ '018f0000-0000-7000-8000-000000000010',
556
+ '11111111-1111-4111-8111-111111111111',
557
+ path.join(tempRoot, 'granular-checkpoint'),
558
+ );
559
+ const repoKey = project.repoKey;
281
560
  const baseOid = 'c'.repeat(40);
282
561
  const largePatch = [
283
562
  'diff --git a/blob.bin b/blob.bin',
@@ -329,139 +608,3 @@ test('a small edit after a large file reuses the large file payload', () => {
329
608
  assert.equal(decoded.state, 'ready');
330
609
  assert.equal(decoded.worktreeDiff, `${largePatch}${smallPatch}`);
331
610
  });
332
-
333
- test('a clean losing commit travels as parked metadata on a no-op checkpoint', () => {
334
- const sharedValue = { revision: null, indexDiff: '', worktreeDiff: '' };
335
- const parked = [{
336
- oid: 'a'.repeat(40),
337
- ref: `refs/amalgm/parked/${'a'.repeat(40)}`,
338
- objects: { kind: 'bundle', checksum: 'b'.repeat(64) },
339
- }];
340
- const checkpoint = _test.checkpointForSharedState({
341
- machine: 'machine-a',
342
- repoKey: 'root:test',
343
- branch: 'main',
344
- baseOid: 'd'.repeat(40),
345
- sharedValue,
346
- parked,
347
- capturedAt: '2026-07-24T00:00:00.000Z',
348
- });
349
-
350
- assert.equal(checkpoint.checkpoint, CHECKPOINT_CONTRACT);
351
- assert.equal(checkpoint.baseOid, 'd'.repeat(40));
352
- assert.equal(checkpoint.branch, 'main');
353
- assert.deepEqual(checkpoint.from, sharedValue);
354
- assert.deepEqual(checkpoint.to, sharedValue);
355
- assert.deepEqual(checkpoint.parked, parked);
356
- });
357
-
358
- test('shared content clears the local pending checkpoint instead of publishing an acknowledgement', () => {
359
- const sharedValue = { revision: 'shared', indexDiff: '', worktreeDiff: 'shared-patch' };
360
- assert.equal(_test.checkpointForSharedState({
361
- machine: 'machine-a',
362
- repoKey: 'root:test',
363
- branch: 'main',
364
- baseOid: 'a'.repeat(40),
365
- sharedValue,
366
- parked: [],
367
- }), null);
368
- });
369
-
370
- test('an acknowledged checkpoint remains a silent receipt until newer state replaces it', () => {
371
- const oid = 'a'.repeat(40);
372
- const checkpoint = {
373
- baseOid: oid,
374
- branch: 'main',
375
- to: { revision: 'local-revision' },
376
- };
377
- const card = { head: { oid, branch: 'main' } };
378
-
379
- assert.equal(
380
- _test.checkpointWasAcknowledged(checkpoint, card, 'local-revision', 51),
381
- true,
382
- );
383
- assert.equal(
384
- _test.checkpointWasAcknowledged(checkpoint, card, 'newer-revision', 51),
385
- false,
386
- );
387
- assert.equal(
388
- _test.checkpointWasAcknowledged(checkpoint, card, 'local-revision', null),
389
- false,
390
- );
391
- });
392
-
393
- test('unpublished parked work uses a no-op checkpoint over the current shared value', () => {
394
- const sharedValue = { revision: 'shared', indexDiff: '', worktreeDiff: 'shared-patch' };
395
- const parked = [{ oid: 'b'.repeat(40), objects: { kind: 'bundle' } }];
396
- const checkpoint = _test.checkpointForSharedState({
397
- machine: 'machine-a',
398
- repoKey: 'root:test',
399
- branch: 'main',
400
- baseOid: 'a'.repeat(40),
401
- sharedValue,
402
- parked,
403
- });
404
-
405
- assert.deepEqual(checkpoint.from, sharedValue);
406
- assert.deepEqual(checkpoint.to, sharedValue);
407
- assert.deepEqual(checkpoint.parked, parked);
408
- });
409
-
410
- test('only this machine keeps announcing its parked commits', () => {
411
- const baseOid = 'a'.repeat(40);
412
- const localOid = 'b'.repeat(40);
413
- const peerOid = 'c'.repeat(40);
414
- const pendingOid = 'd'.repeat(40);
415
- const existing = {
416
- branch: 'main',
417
- baseOid,
418
- parked: [{ oid: localOid, objects: { kind: 'pending' } }],
419
- };
420
- const reduced = {
421
- parked: [
422
- { oid: localOid, version: 10, objects: { kind: 'pending' } },
423
- { oid: peerOid, version: 11, objects: { kind: 'bundle' } },
424
- { oid: pendingOid, objects: { kind: 'pending' } },
425
- ],
426
- };
427
- const card = { head: { branch: 'main', oid: baseOid } };
428
-
429
- assert.deepEqual(
430
- _test.localParkedEntries(existing, reduced, card).map((entry) => entry.oid),
431
- [pendingOid],
432
- );
433
- assert.deepEqual(
434
- _test.localParkedEntries(existing, reduced, {
435
- head: { branch: 'main', oid: 'e'.repeat(40) },
436
- }).map((entry) => entry.oid),
437
- [pendingOid],
438
- );
439
- });
440
-
441
- test('a server rail number does not turn the same parked checkpoint into new work', () => {
442
- const value = { revision: null, indexDiff: '', worktreeDiff: '' };
443
- const checkpoint = {
444
- branch: 'main',
445
- baseOid: 'a'.repeat(40),
446
- from: value,
447
- to: value,
448
- parked: [{
449
- oid: 'b'.repeat(40),
450
- ref: `refs/amalgm/parked/${'b'.repeat(40)}`,
451
- objects: { kind: 'bundle', checksum: 'c'.repeat(64) },
452
- version: 12,
453
- }],
454
- };
455
-
456
- assert.equal(sameTransition(checkpoint, {
457
- ...checkpoint,
458
- parked: checkpoint.parked.map((entry) => ({ ...entry, version: 37 })),
459
- }), true);
460
- assert.equal(sameTransition(checkpoint, {
461
- ...checkpoint,
462
- parked: checkpoint.parked.map((entry) => ({
463
- ...entry,
464
- objects: { ...entry.objects, checksum: 'd'.repeat(64) },
465
- })),
466
- }), false);
467
- });
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+ const test = require('node:test');
8
+ const durable = require('../workspace/durable');
9
+
10
+ test('durable state replacement leaves one complete value and no temporary file', () => {
11
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-durable-'));
12
+ const file = path.join(directory, 'state.json');
13
+ try {
14
+ durable.writeJson(file, { version: 1, value: 'old' });
15
+ durable.writeJson(file, { version: 2, value: 'new' });
16
+ assert.deepEqual(JSON.parse(fs.readFileSync(file, 'utf8')), {
17
+ version: 2,
18
+ value: 'new',
19
+ });
20
+ assert.deepEqual(fs.readdirSync(directory), ['state.json']);
21
+ } finally {
22
+ fs.rmSync(directory, { recursive: true, force: true });
23
+ }
24
+ });