amalgm 0.1.199 → 0.1.201
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 +13 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +26 -0
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +9 -0
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +24 -2
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +1 -0
package/package.json
CHANGED
|
@@ -40,6 +40,19 @@ test.after(() => {
|
|
|
40
40
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
test('checkpoint publication waits until the reducer finishes writing both repo layers', async () => {
|
|
44
|
+
const { withMaterialization } = require('../workspace/reducer');
|
|
45
|
+
const repoKey = 'https://example.test/repo.git';
|
|
46
|
+
|
|
47
|
+
assert.equal(_test.checkpointMayPublish(repoKey), true);
|
|
48
|
+
await withMaterialization(repoKey, async () => {
|
|
49
|
+
assert.equal(_test.checkpointMayPublish(repoKey), false);
|
|
50
|
+
await Promise.resolve();
|
|
51
|
+
assert.equal(_test.checkpointMayPublish(repoKey), false);
|
|
52
|
+
});
|
|
53
|
+
assert.equal(_test.checkpointMayPublish(repoKey), true);
|
|
54
|
+
});
|
|
55
|
+
|
|
43
56
|
async function run(cwd, ...args) {
|
|
44
57
|
await execFileAsync(
|
|
45
58
|
'git',
|
|
@@ -353,6 +353,7 @@ test('peer records and earlier local history survive later publishes', async ()
|
|
|
353
353
|
wall: 'code-project@3',
|
|
354
354
|
project: repoKey,
|
|
355
355
|
machine: 'machine-b',
|
|
356
|
+
mutationId: 'mut_project_peer',
|
|
356
357
|
publishedAt: '2026-07-19T00:00:00.000Z',
|
|
357
358
|
cards: [{ card: 'repo-card@2', machine: 'machine-b', replica: 'rpl_peer', path: '/work/app', head: { branch: 'preview' } }],
|
|
358
359
|
});
|
|
@@ -407,6 +408,7 @@ test('wall compaction deletes only records superseded for every machine', () =>
|
|
|
407
408
|
wall: 'code-project@3',
|
|
408
409
|
project: repoKey,
|
|
409
410
|
machine,
|
|
411
|
+
mutationId: `mut_project_${machine}_${value}`,
|
|
410
412
|
cards: [{ value }],
|
|
411
413
|
});
|
|
412
414
|
const oldA = line('machine-a', 'old-a');
|
|
@@ -422,12 +424,36 @@ test('wall compaction deletes only records superseded for every machine', () =>
|
|
|
422
424
|
assert.equal(compacted.includes(oldB), false);
|
|
423
425
|
});
|
|
424
426
|
|
|
427
|
+
test('an unordered legacy record cannot pin wall compaction', () => {
|
|
428
|
+
const unordered = JSON.stringify({
|
|
429
|
+
wall: 'code-project@3',
|
|
430
|
+
project: repoKey,
|
|
431
|
+
machine: '16.612Z',
|
|
432
|
+
cards: [{ value: 'corrupted-legacy-record' }],
|
|
433
|
+
});
|
|
434
|
+
const ordered = (machine) => JSON.stringify({
|
|
435
|
+
wall: 'code-project@3',
|
|
436
|
+
project: repoKey,
|
|
437
|
+
machine,
|
|
438
|
+
mutationId: `mut_project_${machine}`,
|
|
439
|
+
cards: [{ value: machine }],
|
|
440
|
+
});
|
|
441
|
+
const latestA = ordered('machine-a');
|
|
442
|
+
const latestB = ordered('machine-b');
|
|
443
|
+
|
|
444
|
+
assert.equal(
|
|
445
|
+
compactWallText(`${unordered}\n${latestA}\n${latestB}\n`, repoKey),
|
|
446
|
+
`${latestA}\n${latestB}\n`,
|
|
447
|
+
);
|
|
448
|
+
});
|
|
449
|
+
|
|
425
450
|
test('a concurrent deletion-only compaction and append commute in Yjs', () => {
|
|
426
451
|
const Y = require('yjs');
|
|
427
452
|
const line = (machine, value) => JSON.stringify({
|
|
428
453
|
wall: 'code-project@3',
|
|
429
454
|
project: repoKey,
|
|
430
455
|
machine,
|
|
456
|
+
mutationId: `mut_project_${machine}_${value}`,
|
|
431
457
|
cards: [{ value }],
|
|
432
458
|
});
|
|
433
459
|
const oldA = line('machine-a', 'old-a');
|
|
@@ -188,6 +188,10 @@ function checkpointBaseReady(card, reduced) {
|
|
|
188
188
|
);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
function checkpointMayPublish(repoKey) {
|
|
192
|
+
return !require('./reducer').isMaterializing(repoKey);
|
|
193
|
+
}
|
|
194
|
+
|
|
191
195
|
async function publishPass(replicaId = null) {
|
|
192
196
|
const machine = machineId();
|
|
193
197
|
const run = { published: 0, skips: 0, waiting: 0, failures: 0 };
|
|
@@ -196,6 +200,10 @@ async function publishPass(replicaId = null) {
|
|
|
196
200
|
if (card.health !== 'healthy' || card.kind !== 'primary' || card.operation !== 'none') continue;
|
|
197
201
|
const repoKey = repoKeyOfCard(card);
|
|
198
202
|
if (!repoKey || !card.head?.oid) continue;
|
|
203
|
+
if (!checkpointMayPublish(repoKey)) {
|
|
204
|
+
run.waiting += 1;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
199
207
|
try {
|
|
200
208
|
const reduced = require('./reducer').readRepoState(repoKey);
|
|
201
209
|
if (!checkpointBaseReady(card, reduced)) {
|
|
@@ -376,6 +384,7 @@ module.exports = {
|
|
|
376
384
|
_test: {
|
|
377
385
|
checkpointBaseReady,
|
|
378
386
|
checkpointForSharedState,
|
|
387
|
+
checkpointMayPublish,
|
|
379
388
|
},
|
|
380
389
|
CHECKPOINT_CONTRACT,
|
|
381
390
|
MAX_INLINE_DIFF_BYTES,
|
|
@@ -50,6 +50,20 @@ const service = {
|
|
|
50
50
|
repos: new Map(),
|
|
51
51
|
lastPass: null,
|
|
52
52
|
};
|
|
53
|
+
const materializing = new Set();
|
|
54
|
+
|
|
55
|
+
function isMaterializing(repoKey) {
|
|
56
|
+
return materializing.has(repoKey);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function withMaterialization(repoKey, work) {
|
|
60
|
+
materializing.add(repoKey);
|
|
61
|
+
try {
|
|
62
|
+
return await work();
|
|
63
|
+
} finally {
|
|
64
|
+
materializing.delete(repoKey);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
53
67
|
|
|
54
68
|
function stateFile(repoKey) {
|
|
55
69
|
const digest = crypto.createHash('sha256').update(repoKey).digest('hex').slice(0, 24);
|
|
@@ -587,7 +601,10 @@ async function runReducer() {
|
|
|
587
601
|
if (!plan.card) continue;
|
|
588
602
|
try {
|
|
589
603
|
if (!local?.head?.oid) {
|
|
590
|
-
const hydrated = await
|
|
604
|
+
const hydrated = await withMaterialization(
|
|
605
|
+
repoKey,
|
|
606
|
+
() => hydrateRepo(wall.projectForRepo(repoKey), plan),
|
|
607
|
+
);
|
|
591
608
|
service.repos.set(repoKey, {
|
|
592
609
|
repoKey,
|
|
593
610
|
path: wall.projectForRepo(repoKey)?.path || local?.path || null,
|
|
@@ -627,7 +644,10 @@ async function runReducer() {
|
|
|
627
644
|
service.counters.waiting += 1;
|
|
628
645
|
continue;
|
|
629
646
|
}
|
|
630
|
-
const outcome = await
|
|
647
|
+
const outcome = await withMaterialization(
|
|
648
|
+
repoKey,
|
|
649
|
+
() => materializeRepo(refreshed, currentPlan),
|
|
650
|
+
);
|
|
631
651
|
service.repos.set(repoKey, { repoKey, path: local.path, ...outcome, at: new Date().toISOString() });
|
|
632
652
|
if (outcome.state === 'waiting') service.counters.waiting += 1;
|
|
633
653
|
if (outcome.moved) {
|
|
@@ -718,6 +738,7 @@ module.exports = {
|
|
|
718
738
|
advanceIndex,
|
|
719
739
|
captureIsOnRail,
|
|
720
740
|
hydrateRepo,
|
|
741
|
+
isMaterializing,
|
|
721
742
|
localCardMayMaterialize,
|
|
722
743
|
materializeRepo,
|
|
723
744
|
readRepoState,
|
|
@@ -727,4 +748,5 @@ module.exports = {
|
|
|
727
748
|
reducerStats,
|
|
728
749
|
startWorkspaceReducer,
|
|
729
750
|
stopWorkspaceReducer,
|
|
751
|
+
withMaterialization,
|
|
730
752
|
};
|