amalgm 0.1.185 → 0.1.187
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 +64 -0
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +45 -2
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +12 -2
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
|
+
});
|
|
@@ -116,6 +116,34 @@ test('wall reduction chooses one rail winner and orders checkpoint transitions b
|
|
|
116
116
|
assert.deepEqual(plan.transitions.map((item) => item.version), [8]);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
+
test('a ready parked bundle never regresses to pending', () => {
|
|
120
|
+
const oid = 'c'.repeat(40);
|
|
121
|
+
const ready = card('/ready', 'a'.repeat(40), 1);
|
|
122
|
+
const readyCheckpoint = transition('machine-ready', 2, ready.head.oid, '', '');
|
|
123
|
+
readyCheckpoint.parked = [{
|
|
124
|
+
oid,
|
|
125
|
+
ref: `refs/amalgm/parked/${oid}`,
|
|
126
|
+
objects: { kind: 'bundle', checksum: 'd'.repeat(64) },
|
|
127
|
+
}];
|
|
128
|
+
ready.checkpoints = [readyCheckpoint];
|
|
129
|
+
|
|
130
|
+
const pending = card('/pending', ready.head.oid, 3);
|
|
131
|
+
const pendingCheckpoint = transition('machine-pending', 4, pending.head.oid, '', '');
|
|
132
|
+
pendingCheckpoint.parked = [{
|
|
133
|
+
oid,
|
|
134
|
+
ref: `refs/amalgm/parked/${oid}`,
|
|
135
|
+
objects: { kind: 'pending' },
|
|
136
|
+
}];
|
|
137
|
+
pending.checkpoints = [pendingCheckpoint];
|
|
138
|
+
|
|
139
|
+
const plan = reduceWall([
|
|
140
|
+
{ machine: 'machine-ready', cards: [ready] },
|
|
141
|
+
{ machine: 'machine-pending', cards: [pending] },
|
|
142
|
+
]).get('root:test');
|
|
143
|
+
assert.equal(plan.parked.length, 1);
|
|
144
|
+
assert.equal(plan.parked[0].objects.kind, 'bundle');
|
|
145
|
+
});
|
|
146
|
+
|
|
119
147
|
test('a stale lower rail card cannot roll back a newer branch and commit', () => {
|
|
120
148
|
const newer = card('/newer', 'b'.repeat(40), 12);
|
|
121
149
|
newer.head.branch = 'feature';
|
|
@@ -450,6 +478,42 @@ test('a losing commit is parked, the winning card lands exactly, and losing work
|
|
|
450
478
|
assert.deepEqual(readRepoState('root:test').parked.map((entry) => entry.oid), [losingOid]);
|
|
451
479
|
});
|
|
452
480
|
|
|
481
|
+
test('a local commit made after the verified card snapshot is never parked or overwritten', async () => {
|
|
482
|
+
const { repo, oid: baseOid } = await makeRepo('card-snapshot-race');
|
|
483
|
+
const verifiedCard = card(repo, baseOid, 1);
|
|
484
|
+
|
|
485
|
+
await git(repo, 'switch', '--detach', baseOid);
|
|
486
|
+
fs.writeFileSync(path.join(repo, 'winner.txt'), 'winning commit work\n');
|
|
487
|
+
await git(repo, 'add', '.');
|
|
488
|
+
await git(repo, 'commit', '-m', 'remote winning commit');
|
|
489
|
+
const winningOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
|
|
490
|
+
await git(repo, 'switch', '-C', 'main', baseOid);
|
|
491
|
+
|
|
492
|
+
fs.writeFileSync(path.join(repo, 'late-local.txt'), 'created after verification\n');
|
|
493
|
+
await git(repo, 'add', '.');
|
|
494
|
+
await git(repo, 'commit', '-m', 'late local commit');
|
|
495
|
+
const lateLocalOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
|
|
496
|
+
|
|
497
|
+
const outcome = await materializeRepo(verifiedCard, {
|
|
498
|
+
repoKey: 'root:test',
|
|
499
|
+
card: {
|
|
500
|
+
...verifiedCard,
|
|
501
|
+
head: { branch: 'main', oid: winningOid, detached: false },
|
|
502
|
+
positionVersion: 2,
|
|
503
|
+
positionMutationId: 'mut_card_snapshot_race',
|
|
504
|
+
},
|
|
505
|
+
transitions: [],
|
|
506
|
+
parked: [],
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
assert.equal(outcome.state, 'waiting');
|
|
510
|
+
assert.equal(outcome.reason, 'local-card-changed-after-verification');
|
|
511
|
+
assert.equal((await git(repo, 'rev-parse', 'HEAD')).trim(), lateLocalOid);
|
|
512
|
+
await assert.rejects(
|
|
513
|
+
git(repo, 'rev-parse', `refs/amalgm/parked/${lateLocalOid}`),
|
|
514
|
+
);
|
|
515
|
+
});
|
|
516
|
+
|
|
453
517
|
test('a new card folds checkpoint work already on the rail instead of replaying it as local work', async () => {
|
|
454
518
|
const { repo, oid: baseOid } = await makeRepo('card-folds-shared-checkpoint');
|
|
455
519
|
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,
|
|
@@ -124,6 +124,7 @@ function reduceWall(entries) {
|
|
|
124
124
|
&& entry.branch === repo.card.head?.branch
|
|
125
125
|
))
|
|
126
126
|
.sort((left, right) => left.version - right.version);
|
|
127
|
+
repo.parked = uniqueParked(repo.parked);
|
|
127
128
|
if (repo.card.head?.branch) {
|
|
128
129
|
const held = repo.branchMap.get(repo.card.head.branch);
|
|
129
130
|
if (!held || repo.card.positionVersion > held.version) {
|
|
@@ -146,7 +147,10 @@ function uniqueParked(entries) {
|
|
|
146
147
|
const byOid = new Map();
|
|
147
148
|
for (const entry of entries || []) {
|
|
148
149
|
if (typeof entry?.oid === 'string' && /^[0-9a-f]{40,64}$/.test(entry.oid)) {
|
|
149
|
-
byOid.
|
|
150
|
+
const held = byOid.get(entry.oid);
|
|
151
|
+
if (!held || held.objects?.kind !== 'bundle' || entry.objects?.kind === 'bundle') {
|
|
152
|
+
byOid.set(entry.oid, entry);
|
|
153
|
+
}
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
156
|
return [...byOid.values()].sort((left, right) => left.oid.localeCompare(right.oid));
|
|
@@ -206,6 +210,12 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
206
210
|
const current = await headAndDirty(localCard.path);
|
|
207
211
|
const operation = await operationState(localCard.path);
|
|
208
212
|
if (operation !== 'none') return { state: 'waiting', reason: 'local-operation-in-flight' };
|
|
213
|
+
if (
|
|
214
|
+
current.head.oid !== localCard.head?.oid
|
|
215
|
+
|| current.head.branch !== localCard.head?.branch
|
|
216
|
+
) {
|
|
217
|
+
return { state: 'waiting', reason: 'local-card-changed-after-verification' };
|
|
218
|
+
}
|
|
209
219
|
if (
|
|
210
220
|
current.head.oid === targetCard.head?.oid
|
|
211
221
|
&& current.head.branch === targetCard.head?.branch
|
|
@@ -448,7 +458,7 @@ function captureIsOnRail(plan, captured, localMachine = machineId()) {
|
|
|
448
458
|
}
|
|
449
459
|
|
|
450
460
|
async function materializeRepo(localCardInput, plan) {
|
|
451
|
-
const refreshed =
|
|
461
|
+
const refreshed = localCardInput;
|
|
452
462
|
const cardResult = await moveToWinningCard(refreshed, plan.card);
|
|
453
463
|
if (cardResult.state !== 'ready') return cardResult;
|
|
454
464
|
|