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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.185",
3
+ "version": "0.1.187",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -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, but do not invent an echo operation.
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 ? { ...existing, parked } : null;
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.set(entry.oid, entry);
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 = await refreshRepoCard(localCardInput.replica) || localCardInput;
461
+ const refreshed = localCardInput;
452
462
  const cardResult = await moveToWinningCard(refreshed, plan.card);
453
463
  if (cardResult.state !== 'ready') return cardResult;
454
464