amalgm 0.1.184 → 0.1.186
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 +1 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +36 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +71 -0
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +45 -2
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +3 -1
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +7 -1
package/package.json
CHANGED
|
@@ -27,6 +27,8 @@ const { captureCheckpoint, captureWorktreeDiff } = require('../workspace/git');
|
|
|
27
27
|
const merge = require('../workspace/merge');
|
|
28
28
|
const { applyPatch } = require('../workspace/transition');
|
|
29
29
|
const {
|
|
30
|
+
_test,
|
|
31
|
+
CHECKPOINT_CONTRACT,
|
|
30
32
|
checkpointFile,
|
|
31
33
|
checkpointValueReady,
|
|
32
34
|
readCheckpointValue,
|
|
@@ -180,3 +182,37 @@ test('an uploading checkpoint payload is not published as a transition', () => {
|
|
|
180
182
|
assert.equal(readCheckpointValue('c'.repeat(40), pending).state, 'payload-pending');
|
|
181
183
|
assert.equal(checkpointValueReady({ revision: null, indexDiff: '', worktreeDiff: '' }), true);
|
|
182
184
|
});
|
|
185
|
+
|
|
186
|
+
test('a clean losing commit travels as parked metadata on a no-op checkpoint', () => {
|
|
187
|
+
const sharedValue = { revision: null, indexDiff: '', worktreeDiff: '' };
|
|
188
|
+
const parked = [{
|
|
189
|
+
oid: 'a'.repeat(40),
|
|
190
|
+
ref: `refs/amalgm/parked/${'a'.repeat(40)}`,
|
|
191
|
+
objects: { kind: 'bundle', checksum: 'b'.repeat(64) },
|
|
192
|
+
}];
|
|
193
|
+
const checkpoint = _test.checkpointForSharedState({
|
|
194
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
195
|
+
machine: 'machine-a',
|
|
196
|
+
repoKey: 'root:test',
|
|
197
|
+
branch: 'old',
|
|
198
|
+
baseOid: 'c'.repeat(40),
|
|
199
|
+
from: sharedValue,
|
|
200
|
+
to: sharedValue,
|
|
201
|
+
parked: [],
|
|
202
|
+
}, {
|
|
203
|
+
machine: 'machine-a',
|
|
204
|
+
repoKey: 'root:test',
|
|
205
|
+
branch: 'main',
|
|
206
|
+
baseOid: 'd'.repeat(40),
|
|
207
|
+
sharedValue,
|
|
208
|
+
parked,
|
|
209
|
+
capturedAt: '2026-07-24T00:00:00.000Z',
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
assert.equal(checkpoint.checkpoint, CHECKPOINT_CONTRACT);
|
|
213
|
+
assert.equal(checkpoint.baseOid, 'd'.repeat(40));
|
|
214
|
+
assert.equal(checkpoint.branch, 'main');
|
|
215
|
+
assert.deepEqual(checkpoint.from, sharedValue);
|
|
216
|
+
assert.deepEqual(checkpoint.to, sharedValue);
|
|
217
|
+
assert.deepEqual(checkpoint.parked, parked);
|
|
218
|
+
});
|
|
@@ -309,6 +309,41 @@ test('one materializer converges to the reduced checkpoint and later removes a r
|
|
|
309
309
|
assert.equal(fs.readFileSync(path.join(repo, 'b.txt'), 'utf8'), 'b1\n');
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
test('a mode-only checkpoint makes the peer working file executable', async () => {
|
|
313
|
+
const { repo, oid } = await makeRepo('materialize-mode');
|
|
314
|
+
fs.chmodSync(path.join(repo, 'a.txt'), 0o755);
|
|
315
|
+
const modePatch = await git(repo, 'diff', 'HEAD', '--binary', '--full-index');
|
|
316
|
+
await git(repo, 'reset', '--hard', 'HEAD');
|
|
317
|
+
const localCard = card(repo, oid, 1);
|
|
318
|
+
const clean = { revision: null, indexDiff: '', worktreeDiff: '' };
|
|
319
|
+
const executable = {
|
|
320
|
+
revision: revisionOf(oid, '', modePatch),
|
|
321
|
+
indexDiff: '',
|
|
322
|
+
worktreeDiff: modePatch,
|
|
323
|
+
};
|
|
324
|
+
const plan = {
|
|
325
|
+
repoKey: 'root:test',
|
|
326
|
+
card: localCard,
|
|
327
|
+
transitions: [{
|
|
328
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
329
|
+
machine: 'machine-remote',
|
|
330
|
+
repoKey: 'root:test',
|
|
331
|
+
branch: 'main',
|
|
332
|
+
baseOid: oid,
|
|
333
|
+
from: clean,
|
|
334
|
+
to: executable,
|
|
335
|
+
version: 2,
|
|
336
|
+
mutationId: 'mut_mode_only_02',
|
|
337
|
+
}],
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const outcome = await materializeRepo(localCard, plan);
|
|
341
|
+
assert.equal(outcome.state, 'in-sync');
|
|
342
|
+
assert.notEqual(fs.statSync(path.join(repo, 'a.txt')).mode & 0o100, 0);
|
|
343
|
+
assert.match(await git(repo, 'diff', '--binary', '--full-index'), /new mode 100755/);
|
|
344
|
+
assert.equal(await git(repo, 'diff', '--cached', '--binary', '--full-index'), '');
|
|
345
|
+
});
|
|
346
|
+
|
|
312
347
|
test('once local work is on the rail, a checkpoint conflict materializes exactly without echoing', async () => {
|
|
313
348
|
const { repo, oid } = await makeRepo('materialize-conflict');
|
|
314
349
|
const localPatch = await patchFor(repo, { 'a.txt': 'local answer\n' });
|
|
@@ -415,6 +450,42 @@ test('a losing commit is parked, the winning card lands exactly, and losing work
|
|
|
415
450
|
assert.deepEqual(readRepoState('root:test').parked.map((entry) => entry.oid), [losingOid]);
|
|
416
451
|
});
|
|
417
452
|
|
|
453
|
+
test('a local commit made after the verified card snapshot is never parked or overwritten', async () => {
|
|
454
|
+
const { repo, oid: baseOid } = await makeRepo('card-snapshot-race');
|
|
455
|
+
const verifiedCard = card(repo, baseOid, 1);
|
|
456
|
+
|
|
457
|
+
await git(repo, 'switch', '--detach', baseOid);
|
|
458
|
+
fs.writeFileSync(path.join(repo, 'winner.txt'), 'winning commit work\n');
|
|
459
|
+
await git(repo, 'add', '.');
|
|
460
|
+
await git(repo, 'commit', '-m', 'remote winning commit');
|
|
461
|
+
const winningOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
|
|
462
|
+
await git(repo, 'switch', '-C', 'main', baseOid);
|
|
463
|
+
|
|
464
|
+
fs.writeFileSync(path.join(repo, 'late-local.txt'), 'created after verification\n');
|
|
465
|
+
await git(repo, 'add', '.');
|
|
466
|
+
await git(repo, 'commit', '-m', 'late local commit');
|
|
467
|
+
const lateLocalOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
|
|
468
|
+
|
|
469
|
+
const outcome = await materializeRepo(verifiedCard, {
|
|
470
|
+
repoKey: 'root:test',
|
|
471
|
+
card: {
|
|
472
|
+
...verifiedCard,
|
|
473
|
+
head: { branch: 'main', oid: winningOid, detached: false },
|
|
474
|
+
positionVersion: 2,
|
|
475
|
+
positionMutationId: 'mut_card_snapshot_race',
|
|
476
|
+
},
|
|
477
|
+
transitions: [],
|
|
478
|
+
parked: [],
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
assert.equal(outcome.state, 'waiting');
|
|
482
|
+
assert.equal(outcome.reason, 'local-card-changed-after-verification');
|
|
483
|
+
assert.equal((await git(repo, 'rev-parse', 'HEAD')).trim(), lateLocalOid);
|
|
484
|
+
await assert.rejects(
|
|
485
|
+
git(repo, 'rev-parse', `refs/amalgm/parked/${lateLocalOid}`),
|
|
486
|
+
);
|
|
487
|
+
});
|
|
488
|
+
|
|
418
489
|
test('a new card folds checkpoint work already on the rail instead of replaying it as local work', async () => {
|
|
419
490
|
const { repo, oid: baseOid } = await makeRepo('card-folds-shared-checkpoint');
|
|
420
491
|
const sharedPatch = await patchFor(repo, { 'a.txt': 'shared checkpoint\n' });
|
|
@@ -123,6 +123,32 @@ function checkpointValueReady(value) {
|
|
|
123
123
|
return !value?.payload || value.payload.kind === 'chunks';
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
function checkpointForSharedState(existing, {
|
|
127
|
+
machine,
|
|
128
|
+
repoKey,
|
|
129
|
+
branch,
|
|
130
|
+
baseOid,
|
|
131
|
+
sharedValue,
|
|
132
|
+
parked,
|
|
133
|
+
capturedAt = new Date().toISOString(),
|
|
134
|
+
}) {
|
|
135
|
+
if (existing?.baseOid === baseOid && existing.branch === branch) {
|
|
136
|
+
return { ...existing, parked };
|
|
137
|
+
}
|
|
138
|
+
if (parked.length === 0) return null;
|
|
139
|
+
return {
|
|
140
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
141
|
+
machine,
|
|
142
|
+
repoKey,
|
|
143
|
+
branch,
|
|
144
|
+
baseOid,
|
|
145
|
+
from: sharedValue,
|
|
146
|
+
to: sharedValue,
|
|
147
|
+
parked,
|
|
148
|
+
capturedAt,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
126
152
|
function readCheckpointValue(baseOid, value) {
|
|
127
153
|
let indexDiff = typeof value?.indexDiff === 'string' ? value.indexDiff : '';
|
|
128
154
|
let worktreeDiff = typeof value?.worktreeDiff === 'string' ? value.worktreeDiff : '';
|
|
@@ -191,9 +217,23 @@ async function publishPass(replicaId = null) {
|
|
|
191
217
|
|
|
192
218
|
// Exact equality means the working tree merely materialized the shared
|
|
193
219
|
// checkpoint. Keep the writer's last transition because it remains part
|
|
194
|
-
// of the reducer input
|
|
220
|
+
// of the reducer input. A newly parked commit still rides as metadata on
|
|
221
|
+
// a no-op transition; the working tree itself is never echoed.
|
|
195
222
|
if (sharedRevision === localRevision) {
|
|
196
|
-
const refreshed = existing
|
|
223
|
+
const refreshed = checkpointForSharedState(existing, {
|
|
224
|
+
machine,
|
|
225
|
+
repoKey,
|
|
226
|
+
branch: card.head.branch,
|
|
227
|
+
baseOid: card.head.oid,
|
|
228
|
+
sharedValue: checkpointValue(
|
|
229
|
+
machine,
|
|
230
|
+
repoKey,
|
|
231
|
+
card.head.oid,
|
|
232
|
+
reduced.indexDiff,
|
|
233
|
+
reduced.worktreeDiff,
|
|
234
|
+
),
|
|
235
|
+
parked,
|
|
236
|
+
});
|
|
197
237
|
if (refreshed && !sameTransition(existing, refreshed)) {
|
|
198
238
|
fs.writeFileSync(file, `${JSON.stringify(refreshed)}\n`);
|
|
199
239
|
appendStateEvent({
|
|
@@ -331,6 +371,9 @@ function checkpointStats() {
|
|
|
331
371
|
}
|
|
332
372
|
|
|
333
373
|
module.exports = {
|
|
374
|
+
_test: {
|
|
375
|
+
checkpointForSharedState,
|
|
376
|
+
},
|
|
334
377
|
CHECKPOINT_CONTRACT,
|
|
335
378
|
MAX_INLINE_DIFF_BYTES,
|
|
336
379
|
checkpointFile,
|
|
@@ -166,7 +166,9 @@ function workMatches(work, entry, blobBytes) {
|
|
|
166
166
|
if (!entry) return work === null;
|
|
167
167
|
if (work === null) return false;
|
|
168
168
|
if (entry.mode === '120000') return work.kind === 'symlink' && work.content.equals(blobBytes);
|
|
169
|
-
return work.kind === 'file'
|
|
169
|
+
return work.kind === 'file'
|
|
170
|
+
&& work.exec === (entry.mode === '100755')
|
|
171
|
+
&& work.content.equals(blobBytes);
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
function isText(buffer) {
|
|
@@ -206,6 +206,12 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
206
206
|
const current = await headAndDirty(localCard.path);
|
|
207
207
|
const operation = await operationState(localCard.path);
|
|
208
208
|
if (operation !== 'none') return { state: 'waiting', reason: 'local-operation-in-flight' };
|
|
209
|
+
if (
|
|
210
|
+
current.head.oid !== localCard.head?.oid
|
|
211
|
+
|| current.head.branch !== localCard.head?.branch
|
|
212
|
+
) {
|
|
213
|
+
return { state: 'waiting', reason: 'local-card-changed-after-verification' };
|
|
214
|
+
}
|
|
209
215
|
if (
|
|
210
216
|
current.head.oid === targetCard.head?.oid
|
|
211
217
|
&& current.head.branch === targetCard.head?.branch
|
|
@@ -448,7 +454,7 @@ function captureIsOnRail(plan, captured, localMachine = machineId()) {
|
|
|
448
454
|
}
|
|
449
455
|
|
|
450
456
|
async function materializeRepo(localCardInput, plan) {
|
|
451
|
-
const refreshed =
|
|
457
|
+
const refreshed = localCardInput;
|
|
452
458
|
const cardResult = await moveToWinningCard(refreshed, plan.card);
|
|
453
459
|
if (cardResult.state !== 'ready') return cardResult;
|
|
454
460
|
|