amalgm 0.1.219 → 0.1.221

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.219",
3
+ "version": "0.1.221",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -176,6 +176,46 @@ test('a checkpoint base replaces covered history and keeps only newer mutations'
176
176
  assert.equal(reduced.checkpointRevision, revisionOf(oid, patchAB, ''));
177
177
  });
178
178
 
179
+ test('an unfinished checkpoint base cannot replace the last complete base', () => {
180
+ const oid = 'a'.repeat(40);
181
+ const complete = card('/complete', oid, 1);
182
+ complete.checkpointBase = {
183
+ checkpoint: CHECKPOINT_CONTRACT,
184
+ baseOid: oid,
185
+ branch: 'main',
186
+ through: 4,
187
+ value: value(oid, 'complete-patch'),
188
+ };
189
+ complete.checkpoints = [
190
+ transition('machine-a', 5, oid, 'complete-patch', 'later-patch'),
191
+ ];
192
+
193
+ const unfinished = card('/unfinished', oid, 1);
194
+ unfinished.checkpointBase = {
195
+ checkpoint: CHECKPOINT_CONTRACT,
196
+ baseOid: oid,
197
+ branch: 'main',
198
+ through: 6,
199
+ value: {
200
+ revision: 'b'.repeat(64),
201
+ payload: {
202
+ kind: 'pending',
203
+ contract: 'opaque-chunks@1',
204
+ checksum: 'c'.repeat(64),
205
+ },
206
+ },
207
+ };
208
+
209
+ const plan = reduceWall([
210
+ { machine: 'machine-a', cards: [complete] },
211
+ { machine: 'machine-b', cards: [unfinished] },
212
+ ]).get('root:test');
213
+
214
+ assert.equal(plan.checkpointBase.through, 4);
215
+ assert.equal(plan.checkpointBaseConflict, false);
216
+ assert.deepEqual(plan.transitions.map((entry) => entry.version), [5]);
217
+ });
218
+
179
219
  test('different snapshots claiming the same rail position stop instead of choosing a winner', () => {
180
220
  const oid = 'a'.repeat(40);
181
221
  const first = card('/first', oid, 1);
@@ -863,6 +903,37 @@ test('the materializer creates, moves, and deletes branch refs without checking
863
903
  assert.equal((await git(repo, 'branch', '--show-current')).trim(), 'main');
864
904
  });
865
905
 
906
+ test('an unpublished local branch delete is not restored while catching up', async () => {
907
+ const { repo, oid } = await makeRepo('pending-branch-delete');
908
+ await git(repo, 'switch', '-c', 'feature');
909
+ fs.writeFileSync(path.join(repo, 'feature.txt'), 'feature\n');
910
+ await git(repo, 'add', '.');
911
+ await git(repo, 'commit', '-m', 'feature');
912
+ const featureOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
913
+ await git(repo, 'switch', 'main');
914
+ await git(repo, 'branch', '-D', 'feature');
915
+
916
+ const localCard = card(repo, oid, 1);
917
+ const plan = {
918
+ repoKey: 'root:pending-branch-delete',
919
+ card: localCard,
920
+ transitions: [],
921
+ parked: [],
922
+ branches: [
923
+ { name: 'main', oid, version: 1 },
924
+ { name: 'feature', oid: featureOid, version: 2 },
925
+ ],
926
+ };
927
+ await materializeRepo(localCard, plan);
928
+ assert.equal((await git(repo, 'rev-parse', 'refs/heads/feature')).trim(), featureOid);
929
+
930
+ await git(repo, 'branch', '-D', 'feature');
931
+ await materializeRepo(localCard, plan);
932
+
933
+ await assert.rejects(() => git(repo, 'rev-parse', 'refs/heads/feature'));
934
+ assert.equal((await git(repo, 'branch', '--show-current')).trim(), 'main');
935
+ });
936
+
866
937
  test('a card branch switch follows branch and OID together and leaves the old branch unchanged', async () => {
867
938
  const { repo, oid: mainOid } = await makeRepo('card-branch-switch');
868
939
  await git(repo, 'checkout', '-b', 'feature');
@@ -453,6 +453,20 @@ test('a checkpoint base replaces history already covered by the applied rail', a
453
453
  assert.deepEqual(settleProject(), [3]);
454
454
  });
455
455
 
456
+ test('an uploading checkpoint base is not announced as complete', () => {
457
+ const card = readWall(repoKey)[0].cards[0];
458
+ const [stamped] = stampCards([card], [], { card, branches: [], transitions: [] }, {
459
+ repoKey,
460
+ baseOid: card.head.oid,
461
+ branch: card.head.branch,
462
+ appliedThrough: 3,
463
+ indexDiff: 'x'.repeat(600 * 1024),
464
+ worktreeDiff: '',
465
+ }, 'mut_project_pending_base', project);
466
+
467
+ assert.equal(stamped.checkpointBase, undefined);
468
+ });
469
+
456
470
  test('one failed project publisher cannot block the next repo', () => {
457
471
  const selected = [{ repoKey: 'root:first' }, { repoKey: 'root:second' }];
458
472
  const calls = [];
@@ -153,6 +153,7 @@ function reduceWall(entries) {
153
153
  .filter((entry) => (
154
154
  entry.baseOid === repo.card.head?.oid
155
155
  && entry.branch === repo.card.head?.branch
156
+ && checkpointValueReady(entry.value)
156
157
  ))
157
158
  .sort((left, right) => right.through - left.through);
158
159
  const newestThrough = matchingBases[0]?.through ?? null;
@@ -426,9 +427,11 @@ async function materializeParked(localCard, entries) {
426
427
  return waiting;
427
428
  }
428
429
 
429
- async function materializeBranches(localCard, plan) {
430
+ async function materializeBranches(localCard, plan, heldBranches = []) {
430
431
  const current = new Map((await localBranches(localCard.path)).map((ref) => [ref.name, ref.oid]));
432
+ const held = new Map(heldBranches.map((ref) => [ref.name, ref.oid]));
431
433
  const waiting = [];
434
+ const pending = [];
432
435
  let moved = false;
433
436
  for (const ref of plan.branches || []) {
434
437
  if (ref.name === plan.card.head?.branch) {
@@ -437,6 +440,12 @@ async function materializeBranches(localCard, plan) {
437
440
  }
438
441
  continue;
439
442
  }
443
+ const currentOid = current.get(ref.name) || null;
444
+ const heldOid = held.get(ref.name) || null;
445
+ if (currentOid !== heldOid && currentOid !== ref.oid) {
446
+ pending.push({ name: ref.name, oid: currentOid });
447
+ continue;
448
+ }
440
449
  if (ref.oid === null) {
441
450
  if (current.has(ref.name)) {
442
451
  await transition.deleteBranchRef(localCard.path, ref.name);
@@ -467,7 +476,7 @@ async function materializeBranches(localCard, plan) {
467
476
  moved = true;
468
477
  }
469
478
  }
470
- return { moved, waiting };
479
+ return { moved, waiting, pending };
471
480
  }
472
481
 
473
482
  async function reduceCheckpoint(localCard, plan) {
@@ -598,7 +607,7 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
598
607
  if (requiredParkedWaiting.length > 0) {
599
608
  return { state: 'waiting', reason: `parked-${requiredParkedWaiting[0].state}` };
600
609
  }
601
- const branchResult = await materializeBranches(localCard, plan);
610
+ const branchResult = await materializeBranches(localCard, plan, previous?.branches);
602
611
  const reduced = await reduceCheckpoint(localCard, plan);
603
612
  if (reduced.state !== 'ready') return reduced;
604
613
 
@@ -701,6 +710,7 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
701
710
  worktreeDiff: stateWorktreeDiff,
702
711
  checkpointVersions: plan.transitions.map((entry) => entry.version),
703
712
  branches: plan.branches || [],
713
+ pendingRefs: branchResult.pending,
704
714
  heldTree: applied.heldTree,
705
715
  heldIndexTree: targetIndex,
706
716
  parked,
@@ -20,6 +20,7 @@ const { listRepoCards } = require('./cards');
20
20
  const {
21
21
  CHECKPOINT_CONTRACT,
22
22
  checkpointValue,
23
+ checkpointValueReady,
23
24
  readCheckpoint,
24
25
  repoKeyOfCard,
25
26
  sameTransition,
@@ -331,6 +332,7 @@ function checkpointBaseFor(card, baseline, project, shared = null) {
331
332
  baseline.indexDiff || '',
332
333
  baseline.worktreeDiff || '',
333
334
  );
335
+ if (!checkpointValueReady(value)) return null;
334
336
  const reusableThrough = shared
335
337
  && shared.baseOid === card.head.oid
336
338
  && shared.branch === card.head.branch