amalgm 0.1.180 → 0.1.182
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 +1 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +48 -2
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +1 -1
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +2 -2
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +46 -11
package/package.json
CHANGED
|
@@ -164,7 +164,7 @@ test('checkpoint captures are local cache; their operations travel on the one wa
|
|
|
164
164
|
assert.equal(first, checkpointFile('m1', 'https://github.com/a/b'));
|
|
165
165
|
assert.notEqual(first, checkpointFile('m2', 'https://github.com/a/b'));
|
|
166
166
|
assert.notEqual(first, checkpointFile('m1', 'https://github.com/a/c'));
|
|
167
|
-
assert.match(first, /repo-checkpoints-
|
|
167
|
+
assert.match(first, /repo-checkpoints-v6/);
|
|
168
168
|
});
|
|
169
169
|
|
|
170
170
|
test('an uploading checkpoint payload is not published as a transition', () => {
|
|
@@ -21,8 +21,9 @@ fs.writeFileSync(
|
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
const { closeLocalDb } = require('../state/db');
|
|
24
|
-
const { revisionOf } = require('../workspace/checkpoint');
|
|
24
|
+
const { CHECKPOINT_CONTRACT, revisionOf } = require('../workspace/checkpoint');
|
|
25
25
|
const {
|
|
26
|
+
captureIsOnRail,
|
|
26
27
|
hydrateRepo,
|
|
27
28
|
materializeRepo,
|
|
28
29
|
readRepoState,
|
|
@@ -72,7 +73,7 @@ function value(baseOid, diff) {
|
|
|
72
73
|
|
|
73
74
|
function transition(machine, version, baseOid, from, to) {
|
|
74
75
|
return {
|
|
75
|
-
checkpoint:
|
|
76
|
+
checkpoint: CHECKPOINT_CONTRACT,
|
|
76
77
|
machine,
|
|
77
78
|
repoKey: 'root:test',
|
|
78
79
|
branch: 'main',
|
|
@@ -129,6 +130,18 @@ test('a stale lower rail card cannot roll back a newer branch and commit', () =>
|
|
|
129
130
|
assert.equal(plan.card.positionVersion, 12);
|
|
130
131
|
});
|
|
131
132
|
|
|
133
|
+
test('checkpoint reduction reads only the current rail contract', () => {
|
|
134
|
+
const current = card('/current-contract', 'a'.repeat(40), 1);
|
|
135
|
+
const stale = transition('machine-local', 2, current.head.oid, '', 'stale');
|
|
136
|
+
stale.checkpoint = 'repo-checkpoint@5';
|
|
137
|
+
current.checkpoints = [
|
|
138
|
+
stale,
|
|
139
|
+
transition('machine-local', 3, current.head.oid, '', 'current'),
|
|
140
|
+
];
|
|
141
|
+
const plan = reduceWall([{ machine: 'machine-local', cards: [current] }]).get('root:test');
|
|
142
|
+
assert.deepEqual(plan.transitions.map((item) => item.version), [3]);
|
|
143
|
+
});
|
|
144
|
+
|
|
132
145
|
test('an old pending payload does not block the later complete checkpoint transition', async () => {
|
|
133
146
|
const { repo, oid } = await makeRepo('pending-payload');
|
|
134
147
|
const diff = await patchFor(repo, { 'a.txt': 'a1\n' });
|
|
@@ -296,6 +309,39 @@ test('one materializer converges to the reduced checkpoint and later removes a r
|
|
|
296
309
|
assert.equal(fs.readFileSync(path.join(repo, 'b.txt'), 'utf8'), 'b1\n');
|
|
297
310
|
});
|
|
298
311
|
|
|
312
|
+
test('once local work is on the rail, a checkpoint conflict materializes exactly without echoing', async () => {
|
|
313
|
+
const { repo, oid } = await makeRepo('materialize-conflict');
|
|
314
|
+
const localPatch = await patchFor(repo, { 'a.txt': 'local answer\n' });
|
|
315
|
+
const remotePatch = await patchFor(repo, { 'a.txt': 'remote answer\n' });
|
|
316
|
+
const localTransition = transition('machine-local', 2, oid, '', localPatch);
|
|
317
|
+
const plan = {
|
|
318
|
+
repoKey: 'root:test',
|
|
319
|
+
card: card(repo, oid, 1),
|
|
320
|
+
transitions: [
|
|
321
|
+
localTransition,
|
|
322
|
+
transition('machine-remote', 3, oid, '', remotePatch),
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
fs.writeFileSync(path.join(repo, 'a.txt'), 'local answer\n');
|
|
327
|
+
await git(repo, 'add', 'a.txt');
|
|
328
|
+
assert.equal(captureIsOnRail(plan, {
|
|
329
|
+
baseOid: oid,
|
|
330
|
+
indexDiff: localPatch,
|
|
331
|
+
worktreeDiff: '',
|
|
332
|
+
}), true);
|
|
333
|
+
|
|
334
|
+
const outcome = await materializeRepo(plan.card, plan);
|
|
335
|
+
assert.equal(outcome.state, 'conflict');
|
|
336
|
+
const first = fs.readFileSync(path.join(repo, 'a.txt'), 'utf8');
|
|
337
|
+
assert.match(first, /local answer/);
|
|
338
|
+
assert.match(first, /remote answer/);
|
|
339
|
+
assert.match(first, /<<<<<<< current/);
|
|
340
|
+
|
|
341
|
+
await materializeRepo(plan.card, plan);
|
|
342
|
+
assert.equal(fs.readFileSync(path.join(repo, 'a.txt'), 'utf8'), first);
|
|
343
|
+
});
|
|
344
|
+
|
|
299
345
|
test('a writer publishing again cannot erase its earlier uncommitted work', async () => {
|
|
300
346
|
const { repo, oid } = await makeRepo('checkpoint-history');
|
|
301
347
|
const patchA = await patchFor(repo, { 'a.txt': 'a1\n' });
|
|
@@ -155,7 +155,7 @@ test('checkpoint transitions append until the next card and carry their official
|
|
|
155
155
|
const file = checkpointFile('machine-a', repoKey);
|
|
156
156
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
157
157
|
const firstCheckpoint = {
|
|
158
|
-
checkpoint: 'repo-checkpoint@
|
|
158
|
+
checkpoint: 'repo-checkpoint@6',
|
|
159
159
|
machine: 'machine-a',
|
|
160
160
|
repoKey,
|
|
161
161
|
branch: card.head.branch,
|
|
@@ -25,7 +25,7 @@ const { machineId } = require('./identity');
|
|
|
25
25
|
const { listRepoCards } = require('./cards');
|
|
26
26
|
const { captureCheckpoint } = require('./git');
|
|
27
27
|
|
|
28
|
-
const CHECKPOINT_CONTRACT = 'repo-checkpoint@
|
|
28
|
+
const CHECKPOINT_CONTRACT = 'repo-checkpoint@6';
|
|
29
29
|
const MAX_INLINE_DIFF_BYTES = 512 * 1024;
|
|
30
30
|
const PUBLISH_QUIET_MS = 25;
|
|
31
31
|
const RECONCILE_INTERVAL_MS = 30_000;
|
|
@@ -54,7 +54,7 @@ function checkpointFile(machine, repoKey) {
|
|
|
54
54
|
.update(`${CHECKPOINT_CONTRACT}|${machine}|${repoKey}`)
|
|
55
55
|
.digest('hex')
|
|
56
56
|
.slice(0, 24);
|
|
57
|
-
return path.join(AMALGM_DIR, 'state', 'repo-checkpoints-
|
|
57
|
+
return path.join(AMALGM_DIR, 'state', 'repo-checkpoints-v6', `${digest}.json`);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function revisionOf(baseOid, indexDiff, worktreeDiff = '') {
|
|
@@ -28,10 +28,12 @@ const {
|
|
|
28
28
|
operationState,
|
|
29
29
|
} = require('./git');
|
|
30
30
|
const {
|
|
31
|
+
CHECKPOINT_CONTRACT,
|
|
31
32
|
readCheckpointValue,
|
|
32
33
|
repoKeyOfCard,
|
|
33
34
|
revisionOf,
|
|
34
35
|
} = require('./checkpoint');
|
|
36
|
+
const { machineId } = require('./identity');
|
|
35
37
|
const merge = require('./merge');
|
|
36
38
|
const transition = require('./transition');
|
|
37
39
|
|
|
@@ -105,7 +107,8 @@ function reduceWall(entries) {
|
|
|
105
107
|
}
|
|
106
108
|
for (const checkpoint of card.checkpoints || []) {
|
|
107
109
|
if (
|
|
108
|
-
|
|
110
|
+
checkpoint.checkpoint === CHECKPOINT_CONTRACT
|
|
111
|
+
&& validVersion(checkpoint.version)
|
|
109
112
|
&& checkpoint.machine === wallEntry.machine
|
|
110
113
|
) {
|
|
111
114
|
repo.transitions.push(checkpoint);
|
|
@@ -416,6 +419,25 @@ async function reduceCheckpoint(localCard, plan) {
|
|
|
416
419
|
};
|
|
417
420
|
}
|
|
418
421
|
|
|
422
|
+
function captureIsOnRail(plan, captured, localMachine = machineId()) {
|
|
423
|
+
if (
|
|
424
|
+
!captured
|
|
425
|
+
|| captured.indexDiff === null
|
|
426
|
+
|| captured.baseOid !== plan.card.head.oid
|
|
427
|
+
) return false;
|
|
428
|
+
const latest = [...plan.transitions]
|
|
429
|
+
.reverse()
|
|
430
|
+
.find((operation) => operation.machine === localMachine);
|
|
431
|
+
return Boolean(
|
|
432
|
+
latest
|
|
433
|
+
&& latest.to?.revision === revisionOf(
|
|
434
|
+
captured.baseOid,
|
|
435
|
+
captured.indexDiff,
|
|
436
|
+
captured.worktreeDiff,
|
|
437
|
+
),
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
419
441
|
async function materializeRepo(localCardInput, plan) {
|
|
420
442
|
const refreshed = await refreshRepoCard(localCardInput.replica) || localCardInput;
|
|
421
443
|
const cardResult = await moveToWinningCard(refreshed, plan.card);
|
|
@@ -436,16 +458,28 @@ async function materializeRepo(localCardInput, plan) {
|
|
|
436
458
|
if (reduced.state !== 'ready') return reduced;
|
|
437
459
|
|
|
438
460
|
const baseTree = await merge.treeOf(localCard.path, plan.card.head.oid);
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
?
|
|
443
|
-
:
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
461
|
+
const captured = await captureCheckpoint(localCard.path);
|
|
462
|
+
const represented = captureIsOnRail(plan, captured);
|
|
463
|
+
const capturedIndex = represented
|
|
464
|
+
? await merge.treeOfPatch(localCard.path, plan.card.head.oid, captured.indexDiff)
|
|
465
|
+
: null;
|
|
466
|
+
const capturedWorktree = represented
|
|
467
|
+
? await merge.treeOfPatch(localCard.path, capturedIndex, captured.worktreeDiff)
|
|
468
|
+
: null;
|
|
469
|
+
const heldWorktree = capturedWorktree || (
|
|
470
|
+
previous?.baseOid === plan.card.head.oid
|
|
471
|
+
&& previous.heldTree
|
|
472
|
+
&& await merge.hasTree(localCard.path, previous.heldTree)
|
|
473
|
+
? previous.heldTree
|
|
474
|
+
: baseTree
|
|
475
|
+
);
|
|
476
|
+
const heldIndex = capturedIndex || (
|
|
477
|
+
previous?.baseOid === plan.card.head.oid
|
|
478
|
+
&& previous.heldIndexTree
|
|
479
|
+
&& await merge.hasTree(localCard.path, previous.heldIndexTree)
|
|
480
|
+
? previous.heldIndexTree
|
|
481
|
+
: baseTree
|
|
482
|
+
);
|
|
449
483
|
const currentIndex = await merge.indexTree(localCard.path);
|
|
450
484
|
const reducedLocalIndex = await merge.reduceTree(
|
|
451
485
|
localCard.path,
|
|
@@ -642,6 +676,7 @@ function reducerStats() {
|
|
|
642
676
|
}
|
|
643
677
|
|
|
644
678
|
module.exports = {
|
|
679
|
+
captureIsOnRail,
|
|
645
680
|
hydrateRepo,
|
|
646
681
|
materializeRepo,
|
|
647
682
|
readRepoState,
|