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
@@ -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, spawn } = require('child_process');
8
+ const { execFile } = require('child_process');
9
9
  const { promisify } = require('util');
10
10
 
11
11
  const execFileAsync = promisify(execFile);
@@ -23,27 +23,18 @@ fs.writeFileSync(
23
23
  );
24
24
 
25
25
  const { closeLocalDb } = require('../state/db');
26
- const {
27
- _test: gitTest,
28
- captureCheckpoint,
29
- captureWorktreeDiff,
30
- } = require('../workspace/git');
26
+ const { captureCheckpoint, captureWorktreeDiff } = require('../workspace/git');
31
27
  const merge = require('../workspace/merge');
32
- const { advanceIndex } = require('../workspace/reducer');
28
+ const { applyPatch } = require('../workspace/transition');
33
29
  const {
34
30
  _test,
35
31
  CHECKPOINT_CONTRACT,
36
32
  checkpointFile,
37
- checkpointValue,
38
33
  checkpointValueReady,
39
34
  readCheckpointValue,
40
35
  revisionOf,
41
36
  sameTransition,
42
37
  } = require('../workspace/checkpoint');
43
- const {
44
- acknowledgeUpload,
45
- claimUpload,
46
- } = require('../workspace/objects');
47
38
 
48
39
  test.after(() => {
49
40
  closeLocalDb();
@@ -63,75 +54,6 @@ test('checkpoint publication waits until the reducer finishes writing both repo
63
54
  assert.equal(_test.checkpointMayPublish(repoKey), true);
64
55
  });
65
56
 
66
- test('a checkpoint read across two different HEADs is discarded', () => {
67
- assert.deepEqual(gitTest.checkpointCaptureResult({
68
- baseOid: 'old-head',
69
- observedOid: 'new-head',
70
- indexDiff: '',
71
- worktreeDiff: '',
72
- bytes: 0,
73
- maxBytes: Infinity,
74
- }), {
75
- baseOid: 'old-head',
76
- indexDiff: null,
77
- worktreeDiff: null,
78
- reason: 'head-moved',
79
- bytes: 0,
80
- });
81
- });
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
-
135
57
  async function run(cwd, ...args) {
136
58
  await execFileAsync(
137
59
  'git',
@@ -166,7 +88,7 @@ async function makeRepoPair() {
166
88
  return { author, receiver };
167
89
  }
168
90
 
169
- test('a dirty worktree round-trips through checkpoint trees, gitignore filtering included', async () => {
91
+ test('a dirty worktree round-trips through capture and apply, gitignore filtering included', async () => {
170
92
  const { author, receiver } = await makeRepoPair();
171
93
 
172
94
  // Every dirty shape at once: tracked edit, staged edit, deletion, new
@@ -181,32 +103,13 @@ test('a dirty worktree round-trips through checkpoint trees, gitignore filtering
181
103
  fs.mkdirSync(path.join(author, 'node_modules', 'junk'), { recursive: true });
182
104
  fs.writeFileSync(path.join(author, 'node_modules', 'junk', 'x.js'), 'never travels\n');
183
105
 
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
- );
106
+ const captured = await captureWorktreeDiff(author);
107
+ assert.ok(captured.diff.length > 0);
108
+ assert.ok(!captured.diff.includes('node_modules'), 'ignored files must never enter a checkpoint');
109
+
110
+ const patchFile = path.join(tempRoot, 'roundtrip.patch');
111
+ fs.writeFileSync(patchFile, captured.diff);
112
+ await applyPatch(receiver, patchFile);
210
113
 
211
114
  assert.equal(
212
115
  fs.readFileSync(path.join(receiver, 'README.md'), 'utf8'),
@@ -227,20 +130,8 @@ test('a dirty worktree round-trips through checkpoint trees, gitignore filtering
227
130
  assert.equal(fs.existsSync(path.join(receiver, 'LICENSE')), false, 'deletions travel');
228
131
  assert.equal(fs.existsSync(path.join(receiver, 'node_modules')), false);
229
132
 
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
- );
133
+ // And the reverse of the same patch restores the receiver to clean.
134
+ await applyPatch(receiver, patchFile, { reverse: true });
244
135
  const after = await captureWorktreeDiff(receiver);
245
136
  assert.equal(after.diff, '');
246
137
  });
@@ -254,232 +145,6 @@ test('capture is stable: two reads of an untouched tree produce the same revisio
254
145
  assert.equal(revisionOf(first.baseOid, first.diff), revisionOf(second.baseOid, second.diff));
255
146
  });
256
147
 
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
-
466
- test('checkpoint identity ignores Git file-patch order', async () => {
467
- const { author } = await makeRepoPair();
468
- fs.appendFileSync(path.join(author, 'README.md'), 'tracked edit\n');
469
- fs.writeFileSync(path.join(author, 'A-new-file.txt'), 'untracked edit\n');
470
-
471
- const captured = await captureCheckpoint(author);
472
- const indexTree = await merge.treeOfPatch(author, captured.baseOid, captured.indexDiff);
473
- const worktreeTree = await merge.treeOfPatch(author, indexTree, captured.worktreeDiff);
474
- const normalized = await merge.diffOfTree(author, indexTree, worktreeTree);
475
-
476
- assert.notEqual(captured.worktreeDiff, normalized);
477
- assert.equal(
478
- revisionOf(captured.baseOid, captured.indexDiff, captured.worktreeDiff),
479
- revisionOf(captured.baseOid, captured.indexDiff, normalized),
480
- );
481
- });
482
-
483
148
  test('one checkpoint preserves the staged and unstaged layers separately', async () => {
484
149
  const { author } = await makeRepoPair();
485
150
  fs.writeFileSync(path.join(author, 'src', 'index.js'), 'console.log(2);\n');
@@ -488,11 +153,10 @@ test('one checkpoint preserves the staged and unstaged layers separately', async
488
153
  fs.appendFileSync(path.join(author, 'README.md'), 'unstaged\n');
489
154
 
490
155
  const captured = await captureCheckpoint(author);
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));
156
+ assert.ok(captured.indexDiff.includes('console.log(2)'));
157
+ assert.ok(!captured.indexDiff.includes('console.log(3)'));
158
+ assert.ok(captured.worktreeDiff.includes('console.log(3)'));
159
+ assert.ok(captured.worktreeDiff.includes('unstaged'));
496
160
 
497
161
  const indexTree = await merge.treeOfPatch(author, captured.baseOid, captured.indexDiff);
498
162
  const worktreeTree = await merge.treeOfPatch(author, indexTree, captured.worktreeDiff);
@@ -500,23 +164,6 @@ test('one checkpoint preserves the staged and unstaged layers separately', async
500
164
  assert.equal(await runOutput(author, 'show', `${worktreeTree}:src/index.js`), 'console.log(3);\n');
501
165
  });
502
166
 
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
-
520
167
  test('a clean tree captures as an empty patch, and oversized state is a named refusal', async () => {
521
168
  const { author } = await makeRepoPair();
522
169
  const clean = await captureWorktreeDiff(author);
@@ -533,7 +180,43 @@ test('checkpoint captures are local cache; their operations travel on the one wa
533
180
  assert.equal(first, checkpointFile('m1', 'https://github.com/a/b'));
534
181
  assert.notEqual(first, checkpointFile('m2', 'https://github.com/a/b'));
535
182
  assert.notEqual(first, checkpointFile('m1', 'https://github.com/a/c'));
536
- assert.match(first, /repo-checkpoints-v11/);
183
+ assert.match(first, /repo-checkpoints-v8/);
184
+ });
185
+
186
+ test('a materialized peer card is immediately a valid checkpoint base', () => {
187
+ const oid = 'a'.repeat(40);
188
+ assert.equal(_test.checkpointBaseReady({
189
+ head: { branch: 'main', oid },
190
+ }, {
191
+ appliedThrough: 12,
192
+ branch: 'main',
193
+ baseOid: oid,
194
+ indexDiff: '',
195
+ worktreeDiff: '',
196
+ }, {
197
+ receivedThrough: 12,
198
+ appliedThrough: 12,
199
+ pending: 0,
200
+ current: true,
201
+ }), true);
202
+ });
203
+
204
+ test('checkpoint publication waits when the reducer is behind the rail', () => {
205
+ const oid = 'a'.repeat(40);
206
+ const card = { head: { branch: 'main', oid } };
207
+ const reduced = {
208
+ appliedThrough: 11,
209
+ branch: 'main',
210
+ baseOid: oid,
211
+ indexDiff: '',
212
+ worktreeDiff: '',
213
+ };
214
+ assert.equal(_test.checkpointBaseReady(card, reduced, {
215
+ receivedThrough: 12,
216
+ appliedThrough: 11,
217
+ pending: 0,
218
+ current: false,
219
+ }), false);
537
220
  });
538
221
 
539
222
  test('an uploading checkpoint payload is not published as a transition', () => {
@@ -541,7 +224,7 @@ test('an uploading checkpoint payload is not published as a transition', () => {
541
224
  revision: 'a'.repeat(64),
542
225
  payload: {
543
226
  kind: 'pending',
544
- contract: 'opaque-chunks@2',
227
+ contract: 'opaque-chunks@1',
545
228
  checksum: 'b'.repeat(64),
546
229
  },
547
230
  };
@@ -550,61 +233,95 @@ test('an uploading checkpoint payload is not published as a transition', () => {
550
233
  assert.equal(checkpointValueReady({ revision: null, indexDiff: '', worktreeDiff: '' }), true);
551
234
  });
552
235
 
553
- test('a small edit after a large file reuses the large file payload', () => {
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;
560
- const baseOid = 'c'.repeat(40);
561
- const largePatch = [
562
- 'diff --git a/blob.bin b/blob.bin',
563
- 'new file mode 100644',
564
- '--- /dev/null',
565
- '+++ b/blob.bin',
566
- `+${'x'.repeat(700_000)}`,
567
- '',
568
- ].join('\n');
569
- const smallPatch = [
570
- 'diff --git a/z.txt b/z.txt',
571
- 'new file mode 100644',
572
- '--- /dev/null',
573
- '+++ b/z.txt',
574
- '+small edit',
575
- '',
576
- ].join('\n');
236
+ test('a clean losing commit travels as parked metadata on a no-op checkpoint', () => {
237
+ const sharedValue = { revision: null, indexDiff: '', worktreeDiff: '' };
238
+ const parked = [{
239
+ oid: 'a'.repeat(40),
240
+ ref: `refs/amalgm/parked/${'a'.repeat(40)}`,
241
+ objects: { kind: 'bundle', checksum: 'b'.repeat(64) },
242
+ }];
243
+ const checkpoint = _test.checkpointForSharedState({
244
+ checkpoint: CHECKPOINT_CONTRACT,
245
+ machine: 'machine-a',
246
+ repoKey: 'root:test',
247
+ branch: 'old',
248
+ baseOid: 'c'.repeat(40),
249
+ from: sharedValue,
250
+ to: sharedValue,
251
+ parked: [],
252
+ }, {
253
+ machine: 'machine-a',
254
+ repoKey: 'root:test',
255
+ branch: 'main',
256
+ baseOid: 'd'.repeat(40),
257
+ sharedValue,
258
+ parked,
259
+ capturedAt: '2026-07-24T00:00:00.000Z',
260
+ });
577
261
 
578
- assert.equal(
579
- checkpointValue('machine-a', repoKey, baseOid, '', largePatch).sections.worktree[0].payload.kind,
580
- 'pending',
581
- );
582
- for (;;) {
583
- const upload = claimUpload();
584
- if (!upload) break;
585
- acknowledgeUpload(upload.resourceId, upload.sha256);
586
- }
587
-
588
- const first = checkpointValue('machine-a', repoKey, baseOid, '', largePatch);
589
- assert.equal(checkpointValueReady(first), true);
590
- assert.equal(first.sections.worktree[0].payload.kind, 'chunks');
591
-
592
- const second = checkpointValue(
593
- 'machine-a',
594
- repoKey,
262
+ assert.equal(checkpoint.checkpoint, CHECKPOINT_CONTRACT);
263
+ assert.equal(checkpoint.baseOid, 'd'.repeat(40));
264
+ assert.equal(checkpoint.branch, 'main');
265
+ assert.deepEqual(checkpoint.from, sharedValue);
266
+ assert.deepEqual(checkpoint.to, sharedValue);
267
+ assert.deepEqual(checkpoint.parked, parked);
268
+ });
269
+
270
+ test('only this machine keeps announcing its parked commits', () => {
271
+ const baseOid = 'a'.repeat(40);
272
+ const localOid = 'b'.repeat(40);
273
+ const peerOid = 'c'.repeat(40);
274
+ const pendingOid = 'd'.repeat(40);
275
+ const existing = {
276
+ branch: 'main',
595
277
  baseOid,
596
- '',
597
- `${largePatch}${smallPatch}`,
278
+ parked: [{ oid: localOid, objects: { kind: 'pending' } }],
279
+ };
280
+ const reduced = {
281
+ parked: [
282
+ { oid: localOid, version: 10, objects: { kind: 'pending' } },
283
+ { oid: peerOid, version: 11, objects: { kind: 'bundle' } },
284
+ { oid: pendingOid, objects: { kind: 'pending' } },
285
+ ],
286
+ };
287
+ const card = { head: { branch: 'main', oid: baseOid } };
288
+
289
+ assert.deepEqual(
290
+ _test.localParkedEntries(existing, reduced, card).map((entry) => entry.oid),
291
+ [localOid, pendingOid],
598
292
  );
599
- assert.equal(
600
- checkpointValueReady(second),
601
- true,
602
- 'the unchanged large file must not be uploaded again',
293
+ assert.deepEqual(
294
+ _test.localParkedEntries(existing, reduced, {
295
+ head: { branch: 'main', oid: 'e'.repeat(40) },
296
+ }).map((entry) => entry.oid),
297
+ [pendingOid],
603
298
  );
604
- assert.equal(second.sections.worktree[0].payload.checksum, first.sections.worktree[0].payload.checksum);
605
- assert.equal(claimUpload(), null);
299
+ });
300
+
301
+ test('a server rail number does not turn the same parked checkpoint into new work', () => {
302
+ const value = { revision: null, indexDiff: '', worktreeDiff: '' };
303
+ const checkpoint = {
304
+ branch: 'main',
305
+ baseOid: 'a'.repeat(40),
306
+ from: value,
307
+ to: value,
308
+ parked: [{
309
+ oid: 'b'.repeat(40),
310
+ ref: `refs/amalgm/parked/${'b'.repeat(40)}`,
311
+ objects: { kind: 'bundle', checksum: 'c'.repeat(64) },
312
+ version: 12,
313
+ }],
314
+ };
606
315
 
607
- const decoded = readCheckpointValue(baseOid, second);
608
- assert.equal(decoded.state, 'ready');
609
- assert.equal(decoded.worktreeDiff, `${largePatch}${smallPatch}`);
316
+ assert.equal(sameTransition(checkpoint, {
317
+ ...checkpoint,
318
+ parked: checkpoint.parked.map((entry) => ({ ...entry, version: 37 })),
319
+ }), true);
320
+ assert.equal(sameTransition(checkpoint, {
321
+ ...checkpoint,
322
+ parked: checkpoint.parked.map((entry) => ({
323
+ ...entry,
324
+ objects: { ...entry.objects, checksum: 'd'.repeat(64) },
325
+ })),
326
+ }), false);
610
327
  });