amalgm 0.1.220 → 0.1.234

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.220",
3
+ "version": "0.1.234",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -129,7 +129,13 @@ function acknowledgeAttachment(attachmentIdInput, payload, options = {}) {
129
129
 
130
130
  const installed = payload?.contract === 'workspace-tree@1'
131
131
  ? require('../workspace/tree-store').installCloudTreeSnapshot(attachment.localPath, payload, { database })
132
- : require('./docs').installCloudDocSnapshot(attachment.localPath, payload);
132
+ : payload?.contract === 'code-project-stream@1'
133
+ ? require('../workspace/project-stream').installSnapshot(
134
+ attachment.localPath,
135
+ payload,
136
+ { database },
137
+ )
138
+ : require('./docs').installCloudDocSnapshot(attachment.localPath, payload);
133
139
  const now = new Date().toISOString();
134
140
  cachedStatement(database, `
135
141
  UPDATE shared_resource_attachments
@@ -118,9 +118,35 @@ async function handleWorkspaceContentDiscover(body, sendJson) {
118
118
 
119
119
  async function handlePromotionStart(body, sendJson) {
120
120
  try {
121
+ const codeProject = body?.contract === 'code-project-stream@1';
121
122
  const requestedResourceId = typeof body?.resourceId === 'string' && body.resourceId.trim()
122
123
  ? body.resourceId.trim()
123
124
  : null;
125
+ if (codeProject) {
126
+ if (!requestedResourceId) {
127
+ const error = new Error('code project promotion requires a resourceId');
128
+ error.statusCode = 409;
129
+ throw error;
130
+ }
131
+ const projectStream = require('../workspace/project-stream');
132
+ const localPath = require('path').resolve(body?.path || '');
133
+ const snapshot = projectStream.readSnapshot(localPath, body?.project);
134
+ const bytes = Buffer.from(`${JSON.stringify(snapshot)}\n`, 'utf8');
135
+ const result = require('./promotions').enqueuePromotion({
136
+ promotionId: body?.promotionId || `prm_${crypto.randomBytes(16).toString('base64url')}`,
137
+ resourceId: requestedResourceId,
138
+ localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${requestedResourceId}`,
139
+ localPath,
140
+ contract: projectStream.STREAM_CONTRACT,
141
+ schemaVersion: 1,
142
+ snapshotBase64: bytes.toString('base64'),
143
+ contentBytes: bytes.length,
144
+ name: body?.name,
145
+ mediaType: 'application/vnd.amalgm.code-project+json',
146
+ });
147
+ sendJson(result.duplicate ? 200 : 202, result);
148
+ return;
149
+ }
124
150
  const requestedNodeId = requestedResourceId
125
151
  && require('../workspace/tree/identity').isUuid(requestedResourceId)
126
152
  ? requestedResourceId.toLowerCase()
@@ -214,13 +240,22 @@ async function handlePromotionFail(body, sendJson) {
214
240
  async function handleAttachmentStart(body, sendJson) {
215
241
  try {
216
242
  const tree = body?.contract === 'workspace-tree@1';
217
- const localPath = tree ? require('path').resolve(body?.path || '') : require('./docs').openDoc(body?.path).path;
243
+ const codeProject = body?.contract === 'code-project-stream@1';
244
+ const localPath = tree || codeProject
245
+ ? require('path').resolve(body?.path || '')
246
+ : require('./docs').openDoc(body?.path).path;
218
247
  if (tree) {
219
248
  const stats = fs.statSync(localPath);
220
249
  if (!stats.isDirectory() || fs.readdirSync(localPath).length > 0) {
221
250
  sendJson(409, { error: 'cloud workspace attachment requires an empty folder' });
222
251
  return;
223
252
  }
253
+ } else if (codeProject) {
254
+ const contents = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf8').trim() : '';
255
+ if (contents) {
256
+ sendJson(409, { error: 'code project attachment requires an empty local projection' });
257
+ return;
258
+ }
224
259
  } else if (require('./docs').getDocText(localPath).length > 0) {
225
260
  sendJson(409, { error: 'cloud attachment requires an empty local text file' });
226
261
  return;
@@ -331,7 +366,9 @@ async function handleMutationIngest(body, sendJson) {
331
366
  ? require('../browser/cookie-jar').applyCloudCookieMutation(body)
332
367
  : contract === 'workspace-tree@1'
333
368
  ? require('../workspace/tree-store').applyCloudTreeMutation(body)
334
- : require('./docs').applyCloudDocMutation(body);
369
+ : contract === 'code-project-stream@1'
370
+ ? require('../workspace/project-stream').applyCloudMutation(body)
371
+ : require('./docs').applyCloudDocMutation(body);
335
372
  sendJson(200, result);
336
373
  } catch (error) {
337
374
  sendHandlerError(error, sendJson);
@@ -0,0 +1,180 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert/strict');
5
+ const crypto = require('crypto');
6
+ const fs = require('fs');
7
+ const os = require('os');
8
+ const path = require('path');
9
+
10
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-code-project-stream-'));
11
+ process.env.AMALGM_DIR = path.join(tempRoot, '.amalgm');
12
+ process.env.AMALGM_WORKSPACES_DIR = path.join(process.env.AMALGM_DIR, 'workspaces');
13
+ process.env.AMALGM_COMPUTER_ID = 'machine-a';
14
+ process.env.AMALGM_DEVICE_ID = 'machine-a';
15
+ fs.mkdirSync(process.env.AMALGM_DIR, { recursive: true });
16
+ fs.writeFileSync(
17
+ path.join(process.env.AMALGM_DIR, 'computer.json'),
18
+ JSON.stringify({
19
+ computer_id: 'machine-a',
20
+ user_id: '11111111-1111-4111-8111-111111111111',
21
+ }),
22
+ );
23
+
24
+ const { closeLocalDb } = require('../state/db');
25
+ const attachments = require('../state/attachments');
26
+ const promotions = require('../state/promotions');
27
+ const shared = require('../state/shared-rest');
28
+ const projectStream = require('../workspace/project-stream');
29
+
30
+ test.after(() => {
31
+ closeLocalDb();
32
+ fs.rmSync(tempRoot, { recursive: true, force: true });
33
+ });
34
+
35
+ function call(handler, body) {
36
+ return new Promise((resolve, reject) => {
37
+ handler(body, (status, payload) => {
38
+ if (status >= 400) reject(Object.assign(new Error(payload?.error), { status, payload }));
39
+ else resolve({ status, payload });
40
+ });
41
+ });
42
+ }
43
+
44
+ function record(project, machine, mutationId, marker) {
45
+ return {
46
+ wall: projectStream.WALL_CONTRACT,
47
+ project,
48
+ machine,
49
+ mutationId,
50
+ publishedAt: '2026-07-26T12:00:00.000Z',
51
+ cards: [{ marker }],
52
+ };
53
+ }
54
+
55
+ function encodedSnapshot(snapshot) {
56
+ const bytes = Buffer.from(`${JSON.stringify(snapshot)}\n`, 'utf8');
57
+ return {
58
+ snapshotBase64: bytes.toString('base64'),
59
+ snapshotChecksum: crypto.createHash('sha256').update(bytes).digest('hex'),
60
+ };
61
+ }
62
+
63
+ test('a code project promotes as a compact snapshot, not as a text document', async () => {
64
+ const project = 'root:promotion';
65
+ const resourceId = 'shr_code_project_promotion_0001';
66
+ const file = path.join(tempRoot, 'promotion.json');
67
+ projectStream.writeSnapshot(file, projectStream._test.emptySnapshot(project));
68
+
69
+ const started = await call(shared.handlePromotionStart, {
70
+ resourceId,
71
+ path: file,
72
+ project,
73
+ contract: projectStream.STREAM_CONTRACT,
74
+ });
75
+ assert.equal(started.status, 202);
76
+ assert.equal(started.payload.promotion.contract, projectStream.STREAM_CONTRACT);
77
+ assert.equal(started.payload.promotion.localResource, `${projectStream.LOCAL_RESOURCE_PREFIX}${resourceId}`);
78
+
79
+ const promotion = promotions.getPromotion(started.payload.promotion.promotionId);
80
+ promotions.acknowledgePromotion(promotion.promotionId, {
81
+ resourceId,
82
+ authorityEpoch: 1,
83
+ headVersion: 0,
84
+ snapshotVersion: 0,
85
+ snapshotChecksum: promotion.snapshotChecksum,
86
+ snapshotKey: 'shared/code-project/promotion.bin',
87
+ });
88
+ const replica = require('../state/replicas').getReplicaByResourceId(resourceId);
89
+ assert.equal(replica.contract, projectStream.STREAM_CONTRACT);
90
+ assert.equal(replica.localPath, file);
91
+ });
92
+
93
+ test('empty attachment hydrates through N, then applies only event N+1', async () => {
94
+ const project = 'root:hydrate';
95
+ const resourceId = 'shr_code_project_attachment_0001';
96
+ const file = path.join(tempRoot, 'attachment.json');
97
+ const started = await call(shared.handleAttachmentStart, {
98
+ resourceId,
99
+ path: file,
100
+ contract: projectStream.STREAM_CONTRACT,
101
+ });
102
+ assert.equal(started.status, 202);
103
+
104
+ const mutationA = 'mut_code_project_snapshot_a';
105
+ const snapshot = {
106
+ schemaVersion: 1,
107
+ project,
108
+ version: 2,
109
+ records: [{ ...record(project, 'machine-a', mutationA, 'snapshot'), version: 2 }],
110
+ };
111
+ const encoded = encodedSnapshot(snapshot);
112
+ await call(shared.handleAttachmentAck, {
113
+ attachmentId: started.payload.attachment.attachmentId,
114
+ resourceId,
115
+ authorityEpoch: 1,
116
+ headVersion: 2,
117
+ snapshotVersion: 2,
118
+ contract: projectStream.STREAM_CONTRACT,
119
+ schemaVersion: 1,
120
+ ...encoded,
121
+ });
122
+
123
+ assert.equal(attachments.getAttachmentByResource(resourceId).state, 'attached');
124
+ assert.deepEqual(projectStream.readSnapshot(file, project), snapshot);
125
+
126
+ const mutationB = 'mut_code_project_cloud_b';
127
+ await call(shared.handleMutationIngest, {
128
+ version: 3,
129
+ committedAt: '2026-07-26T12:00:03.000Z',
130
+ envelope: {
131
+ resourceId,
132
+ authorityEpoch: 1,
133
+ mutationId: mutationB,
134
+ deviceId: 'machine-b',
135
+ baseVersion: 2,
136
+ contract: projectStream.STREAM_CONTRACT,
137
+ schemaVersion: 1,
138
+ operationKind: 'code.publish',
139
+ operation: {
140
+ kind: 'code.publish',
141
+ record: record(project, 'machine-b', mutationB, 'event-3'),
142
+ },
143
+ },
144
+ });
145
+ assert.deepEqual(
146
+ projectStream.readSnapshot(file, project).records.map((entry) => [
147
+ entry.machine,
148
+ entry.version,
149
+ entry.cards[0].marker,
150
+ ]),
151
+ [
152
+ ['machine-a', 2, 'snapshot'],
153
+ ['machine-b', 3, 'event-3'],
154
+ ],
155
+ );
156
+
157
+ const beforeGap = fs.readFileSync(file, 'utf8');
158
+ await assert.rejects(
159
+ call(shared.handleMutationIngest, {
160
+ version: 5,
161
+ committedAt: '2026-07-26T12:00:05.000Z',
162
+ envelope: {
163
+ resourceId,
164
+ authorityEpoch: 1,
165
+ mutationId: 'mut_code_project_gap_005',
166
+ deviceId: 'machine-c',
167
+ baseVersion: 4,
168
+ contract: projectStream.STREAM_CONTRACT,
169
+ schemaVersion: 1,
170
+ operationKind: 'code.publish',
171
+ operation: {
172
+ kind: 'code.publish',
173
+ record: record(project, 'machine-c', 'mut_code_project_gap_005', 'gap'),
174
+ },
175
+ },
176
+ }),
177
+ /expected 4, received 5/,
178
+ );
179
+ assert.equal(fs.readFileSync(file, 'utf8'), beforeGap);
180
+ });
@@ -16,6 +16,7 @@ process.env.AMALGM_COMPUTER_ID = 'computer-test';
16
16
 
17
17
  const { closeLocalDb, openLocalDb } = require('../state/db');
18
18
  const {
19
+ _test: cardTest,
19
20
  buildRepoCard,
20
21
  cardFingerprint,
21
22
  discoverWorktrees,
@@ -399,3 +400,49 @@ test('the fingerprint holds across observations and moves on any real change', a
399
400
  const third = await buildRepoCard(dir);
400
401
  assert.notEqual(cardFingerprint(second), cardFingerprint(third));
401
402
  });
403
+
404
+ test('unchanged commits reuse their card object source and one new oid is resolved once', async () => {
405
+ const dir = await makeRepo('object-source-cache');
406
+ await run(dir, 'branch', 'side');
407
+ const local = await buildRepoCard(dir);
408
+ await run(dir, 'remote', 'add', 'origin', 'https://github.com/amalgm/object-source-cache.git');
409
+ const previous = {
410
+ ...local,
411
+ repo: { ...local.repo, remote: 'https://github.com/amalgm/object-source-cache' },
412
+ objects: { kind: 'remote' },
413
+ refs: local.refs.map((ref) => ({ ...ref, objects: { kind: 'remote' } })),
414
+ };
415
+ const objects = require('../workspace/objects');
416
+ const original = objects.objectSourceFor;
417
+ let calls = 0;
418
+ objects.objectSourceFor = async () => {
419
+ calls += 1;
420
+ return { kind: 'pending' };
421
+ };
422
+ try {
423
+ const unchanged = await buildRepoCard(dir, previous);
424
+ assert.equal(calls, 0);
425
+ assert.equal(unchanged.objects.kind, 'remote');
426
+ assert.ok(unchanged.refs.every((ref) => ref.objects.kind === 'remote'));
427
+
428
+ write(dir, 'next.txt', 'next\n');
429
+ await run(dir, 'add', '.');
430
+ await run(dir, 'commit', '-m', 'next');
431
+ const changed = await buildRepoCard(dir, unchanged);
432
+ assert.equal(calls, 1, 'HEAD and its branch share one content-addressed lookup');
433
+ assert.equal(changed.objects.kind, 'pending');
434
+ assert.equal(changed.refs.find((ref) => ref.name === 'main').objects.kind, 'pending');
435
+ assert.equal(changed.refs.find((ref) => ref.name === 'side').objects.kind, 'remote');
436
+ } finally {
437
+ objects.objectSourceFor = original;
438
+ }
439
+ });
440
+
441
+ test('only complete object sources are reusable', () => {
442
+ assert.deepEqual(
443
+ cardTest.reusableObjectSource('a', 'a', { kind: 'bundle', checksum: 'ready' }),
444
+ { kind: 'bundle', checksum: 'ready' },
445
+ );
446
+ assert.equal(cardTest.reusableObjectSource('a', 'b', { kind: 'remote' }), null);
447
+ assert.equal(cardTest.reusableObjectSource('a', 'a', { kind: 'pending' }), null);
448
+ });
@@ -57,51 +57,44 @@ test('rail-ordered checkpoint transitions combine and a later revert removes onl
57
57
  const treeA = await merge.treeOfPatch(repo, oid, patchA);
58
58
  const treeB = await merge.treeOfPatch(repo, oid, patchB);
59
59
 
60
- const afterA = await merge.reduceTree(repo, clean, clean, treeA, { label: '1' });
61
- const afterB = await merge.reduceTree(repo, afterA.tree, clean, treeB, { label: '2' });
60
+ const afterA = await merge.reduceTree(repo, clean, clean, treeA);
61
+ const afterB = await merge.reduceTree(repo, afterA.tree, clean, treeB);
62
62
  assert.equal(await treeText(repo, afterB.tree, 'a.txt'), 'a1\n');
63
63
  assert.equal(await treeText(repo, afterB.tree, 'b.txt'), 'b1\n');
64
- assert.deepEqual(afterB.conflicts, []);
65
64
 
66
65
  // A's next operation says "from A+B to B": the reducer can remove A even
67
66
  // though B's latest checkpoint already incorporated A.
68
- const reverted = await merge.reduceTree(repo, afterB.tree, afterB.tree, treeB, { label: '3' });
67
+ const reverted = await merge.reduceTree(repo, afterB.tree, afterB.tree, treeB);
69
68
  assert.equal(await treeText(repo, reverted.tree, 'a.txt'), 'a0\n');
70
69
  assert.equal(await treeText(repo, reverted.tree, 'b.txt'), 'b1\n');
71
70
  });
72
71
 
73
- test('an overlapping transition produces deterministic conflict bytes', async () => {
74
- const { repo, oid, tree: clean } = await makeRepo('conflict');
72
+ test('a later overlapping transition supplies the exact final bytes', async () => {
73
+ const { repo, oid, tree: clean } = await makeRepo('later-wins');
75
74
  const leftPatch = await patchFor(repo, { 'same.txt': 'left\nline 2\n' });
76
75
  const rightPatch = await patchFor(repo, { 'same.txt': 'right\nline 2\n' });
77
76
  const left = await merge.treeOfPatch(repo, oid, leftPatch);
78
77
  const right = await merge.treeOfPatch(repo, oid, rightPatch);
79
78
 
80
- const current = await merge.reduceTree(repo, clean, clean, left, { label: '10' });
81
- const first = await merge.reduceTree(repo, current.tree, clean, right, { label: '11' });
82
- const second = await merge.reduceTree(repo, current.tree, clean, right, { label: '11' });
79
+ const current = await merge.reduceTree(repo, clean, clean, left);
80
+ const first = await merge.reduceTree(repo, current.tree, clean, right);
81
+ const second = await merge.reduceTree(repo, current.tree, clean, right);
83
82
 
84
83
  assert.equal(first.tree, second.tree);
85
- assert.deepEqual(first.conflicts, second.conflicts);
86
- assert.equal(first.conflicts[0].kind, 'content-overlap');
87
- const content = await treeText(repo, first.tree, 'same.txt');
88
- assert.match(content, /<<<<<<< current/);
89
- assert.match(content, /left/);
90
- assert.match(content, /right/);
84
+ assert.equal(await treeText(repo, first.tree, 'same.txt'), 'right\nline 2\n');
91
85
  });
92
86
 
93
87
  test('an echoed transition that is already present reduces to a no-op', async () => {
94
88
  const { repo, oid, tree: clean } = await makeRepo('echo');
95
89
  const patch = await patchFor(repo, { 'a.txt': 'shared\n' });
96
90
  const shared = await merge.treeOfPatch(repo, oid, patch);
97
- const current = await merge.reduceTree(repo, clean, clean, shared, { label: '20' });
98
- const echoed = await merge.reduceTree(repo, current.tree, clean, shared, { label: '21' });
91
+ const current = await merge.reduceTree(repo, clean, clean, shared);
92
+ const echoed = await merge.reduceTree(repo, current.tree, clean, shared);
99
93
  assert.equal(echoed.tree, current.tree);
100
- assert.deepEqual(echoed.conflicts, []);
101
94
  });
102
95
 
103
- test('a deletion never removes a file another writer edited', async () => {
104
- const { repo, oid, tree: clean } = await makeRepo('delete-vs-edit');
96
+ test('a later deletion removes a file changed by an earlier writer', async () => {
97
+ const { repo, oid, tree: clean } = await makeRepo('later-delete');
105
98
  const localPatch = await patchFor(repo, { 'a.txt': 'precious local edit\n' });
106
99
  const localTree = await merge.treeOfPatch(repo, oid, localPatch);
107
100
  fs.rmSync(path.join(repo, 'a.txt'));
@@ -109,8 +102,19 @@ test('a deletion never removes a file another writer edited', async () => {
109
102
  await git(repo, 'reset', '--hard', 'HEAD');
110
103
  const deletedTree = await merge.treeOfPatch(repo, oid, deletePatch);
111
104
 
112
- const current = await merge.reduceTree(repo, clean, clean, localTree, { label: '30' });
113
- const outcome = await merge.reduceTree(repo, current.tree, clean, deletedTree, { label: '31' });
114
- assert.equal(outcome.conflicts[0].kind, 'delete-vs-change');
115
- assert.equal(await treeText(repo, outcome.tree, 'a.txt'), 'precious local edit\n');
105
+ const current = await merge.reduceTree(repo, clean, clean, localTree);
106
+ const outcome = await merge.reduceTree(repo, current.tree, clean, deletedTree);
107
+ await assert.rejects(treeText(repo, outcome.tree, 'a.txt'));
108
+ });
109
+
110
+ test('an earlier inbound event advances underneath a pending local write', async () => {
111
+ const { repo, oid, tree: clean } = await makeRepo('pending-local');
112
+ const inboundPatch = await patchFor(repo, { 'same.txt': 'inbound\n' });
113
+ const inbound = await merge.treeOfPatch(repo, oid, inboundPatch);
114
+
115
+ fs.writeFileSync(path.join(repo, 'same.txt'), 'pending local\n');
116
+ const outcome = await merge.advance(repo, clean, inbound);
117
+
118
+ assert.equal(fs.readFileSync(path.join(repo, 'same.txt'), 'utf8'), 'pending local\n');
119
+ assert.equal(await treeText(repo, outcome.heldTree, 'same.txt'), 'inbound\n');
116
120
  });
@@ -23,18 +23,27 @@ fs.writeFileSync(
23
23
  );
24
24
 
25
25
  const { closeLocalDb } = require('../state/db');
26
- const { captureCheckpoint, captureWorktreeDiff } = require('../workspace/git');
26
+ const {
27
+ _test: gitTest,
28
+ captureCheckpoint,
29
+ captureWorktreeDiff,
30
+ } = require('../workspace/git');
27
31
  const merge = require('../workspace/merge');
28
32
  const { applyPatch } = require('../workspace/transition');
29
33
  const {
30
34
  _test,
31
35
  CHECKPOINT_CONTRACT,
32
36
  checkpointFile,
37
+ checkpointValue,
33
38
  checkpointValueReady,
34
39
  readCheckpointValue,
35
40
  revisionOf,
36
41
  sameTransition,
37
42
  } = require('../workspace/checkpoint');
43
+ const {
44
+ acknowledgeUpload,
45
+ claimUpload,
46
+ } = require('../workspace/objects');
38
47
 
39
48
  test.after(() => {
40
49
  closeLocalDb();
@@ -54,6 +63,23 @@ test('checkpoint publication waits until the reducer finishes writing both repo
54
63
  assert.equal(_test.checkpointMayPublish(repoKey), true);
55
64
  });
56
65
 
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
+
57
83
  async function run(cwd, ...args) {
58
84
  await execFileAsync(
59
85
  'git',
@@ -145,6 +171,23 @@ test('capture is stable: two reads of an untouched tree produce the same revisio
145
171
  assert.equal(revisionOf(first.baseOid, first.diff), revisionOf(second.baseOid, second.diff));
146
172
  });
147
173
 
174
+ test('checkpoint identity ignores Git file-patch order', async () => {
175
+ const { author } = await makeRepoPair();
176
+ fs.appendFileSync(path.join(author, 'README.md'), 'tracked edit\n');
177
+ fs.writeFileSync(path.join(author, 'A-new-file.txt'), 'untracked edit\n');
178
+
179
+ const captured = await captureCheckpoint(author);
180
+ const indexTree = await merge.treeOfPatch(author, captured.baseOid, captured.indexDiff);
181
+ const worktreeTree = await merge.treeOfPatch(author, indexTree, captured.worktreeDiff);
182
+ const normalized = await merge.diffOfTree(author, indexTree, worktreeTree);
183
+
184
+ assert.notEqual(captured.worktreeDiff, normalized);
185
+ assert.equal(
186
+ revisionOf(captured.baseOid, captured.indexDiff, captured.worktreeDiff),
187
+ revisionOf(captured.baseOid, captured.indexDiff, normalized),
188
+ );
189
+ });
190
+
148
191
  test('one checkpoint preserves the staged and unstaged layers separately', async () => {
149
192
  const { author } = await makeRepoPair();
150
193
  fs.writeFileSync(path.join(author, 'src', 'index.js'), 'console.log(2);\n');
@@ -180,7 +223,7 @@ test('checkpoint captures are local cache; their operations travel on the one wa
180
223
  assert.equal(first, checkpointFile('m1', 'https://github.com/a/b'));
181
224
  assert.notEqual(first, checkpointFile('m2', 'https://github.com/a/b'));
182
225
  assert.notEqual(first, checkpointFile('m1', 'https://github.com/a/c'));
183
- assert.match(first, /repo-checkpoints-v8/);
226
+ assert.match(first, /repo-checkpoints-v9/);
184
227
  });
185
228
 
186
229
  test('a materialized peer card is immediately a valid checkpoint base', () => {
@@ -233,6 +276,60 @@ test('an uploading checkpoint payload is not published as a transition', () => {
233
276
  assert.equal(checkpointValueReady({ revision: null, indexDiff: '', worktreeDiff: '' }), true);
234
277
  });
235
278
 
279
+ test('a small edit after a large file reuses the large file payload', () => {
280
+ const repoKey = 'https://example.test/granular-checkpoint.git';
281
+ const baseOid = 'c'.repeat(40);
282
+ const largePatch = [
283
+ 'diff --git a/blob.bin b/blob.bin',
284
+ 'new file mode 100644',
285
+ '--- /dev/null',
286
+ '+++ b/blob.bin',
287
+ `+${'x'.repeat(700_000)}`,
288
+ '',
289
+ ].join('\n');
290
+ const smallPatch = [
291
+ 'diff --git a/z.txt b/z.txt',
292
+ 'new file mode 100644',
293
+ '--- /dev/null',
294
+ '+++ b/z.txt',
295
+ '+small edit',
296
+ '',
297
+ ].join('\n');
298
+
299
+ assert.equal(
300
+ checkpointValue('machine-a', repoKey, baseOid, '', largePatch).sections.worktree[0].payload.kind,
301
+ 'pending',
302
+ );
303
+ for (;;) {
304
+ const upload = claimUpload();
305
+ if (!upload) break;
306
+ acknowledgeUpload(upload.resourceId, upload.sha256);
307
+ }
308
+
309
+ const first = checkpointValue('machine-a', repoKey, baseOid, '', largePatch);
310
+ assert.equal(checkpointValueReady(first), true);
311
+ assert.equal(first.sections.worktree[0].payload.kind, 'chunks');
312
+
313
+ const second = checkpointValue(
314
+ 'machine-a',
315
+ repoKey,
316
+ baseOid,
317
+ '',
318
+ `${largePatch}${smallPatch}`,
319
+ );
320
+ assert.equal(
321
+ checkpointValueReady(second),
322
+ true,
323
+ 'the unchanged large file must not be uploaded again',
324
+ );
325
+ assert.equal(second.sections.worktree[0].payload.checksum, first.sections.worktree[0].payload.checksum);
326
+ assert.equal(claimUpload(), null);
327
+
328
+ const decoded = readCheckpointValue(baseOid, second);
329
+ assert.equal(decoded.state, 'ready');
330
+ assert.equal(decoded.worktreeDiff, `${largePatch}${smallPatch}`);
331
+ });
332
+
236
333
  test('a clean losing commit travels as parked metadata on a no-op checkpoint', () => {
237
334
  const sharedValue = { revision: null, indexDiff: '', worktreeDiff: '' };
238
335
  const parked = [{
@@ -241,15 +338,6 @@ test('a clean losing commit travels as parked metadata on a no-op checkpoint', (
241
338
  objects: { kind: 'bundle', checksum: 'b'.repeat(64) },
242
339
  }];
243
340
  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
341
  machine: 'machine-a',
254
342
  repoKey: 'root:test',
255
343
  branch: 'main',
@@ -267,6 +355,58 @@ test('a clean losing commit travels as parked metadata on a no-op checkpoint', (
267
355
  assert.deepEqual(checkpoint.parked, parked);
268
356
  });
269
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
+
270
410
  test('only this machine keeps announcing its parked commits', () => {
271
411
  const baseOid = 'a'.repeat(40);
272
412
  const localOid = 'b'.repeat(40);
@@ -288,7 +428,7 @@ test('only this machine keeps announcing its parked commits', () => {
288
428
 
289
429
  assert.deepEqual(
290
430
  _test.localParkedEntries(existing, reduced, card).map((entry) => entry.oid),
291
- [localOid, pendingOid],
431
+ [pendingOid],
292
432
  );
293
433
  assert.deepEqual(
294
434
  _test.localParkedEntries(existing, reduced, {
@@ -129,4 +129,10 @@ test('a large checkpoint uses the same verified chunk lane', () => {
129
129
  const received = readPayload(locator);
130
130
  assert.equal(received.state, 'ready');
131
131
  assert.deepEqual(received.bytes, content);
132
+ assert.equal(
133
+ publishPayload('same-content-after-download', content, 'repo:test').kind,
134
+ 'chunks',
135
+ 'verified downloaded content is immediately reusable by a later checkpoint',
136
+ );
137
+ assert.equal(claimUpload(), null);
132
138
  });