amalgm 0.1.220 → 0.1.234

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.
@@ -29,11 +29,12 @@ const {
29
29
  } = require('../workspace/checkpoint');
30
30
  const { parkedForState } = require('../workspace/reducer');
31
31
  const {
32
- compactWallText,
32
+ _test: wallTest,
33
33
  hasLocalContribution,
34
34
  publicationStatus,
35
35
  stampCards,
36
36
  } = require('../workspace/wall');
37
+ const projectStream = require('../workspace/project-stream');
37
38
 
38
39
  test.after(() => {
39
40
  closeLocalDb();
@@ -65,16 +66,16 @@ const rules = {
65
66
  liveness: 'shared-wall-outbox',
66
67
  gating: 'inside-winning-card-checkpoint',
67
68
  },
68
- compaction: {
69
- identity: 'derived-deletion',
70
- ownership: 'any-machine-may-compact',
71
- liveness: 'not-semantic-state',
72
- gating: 'commutative-with-writers',
69
+ snapshot: {
70
+ identity: 'state-through-official-version',
71
+ ownership: 'authority-derived',
72
+ liveness: 'hydrates-then-replays-tail',
73
+ gating: 'never-a-new-event',
73
74
  },
74
75
  };
75
76
 
76
77
  test('the publication model has one explicit rule for every value and property', () => {
77
- assert.deepEqual(Object.keys(rules), ['card', 'branch', 'checkpoint', 'parked', 'compaction']);
78
+ assert.deepEqual(Object.keys(rules), ['card', 'branch', 'checkpoint', 'parked', 'snapshot']);
78
79
  for (const rule of Object.values(rules)) {
79
80
  assert.deepEqual(Object.keys(rule), ['identity', 'ownership', 'liveness', 'gating']);
80
81
  assert.equal(Object.values(rule).every(Boolean), true);
@@ -87,15 +88,15 @@ test('the publication model has one explicit rule for every value and property',
87
88
 
88
89
  test('publication requires one current applied rail position and no unresolved send', () => {
89
90
  const repoKey = 'root:currentness';
90
- const resourceId = require('../workspace/wall').projectResourceId(repoKey);
91
- const file = path.join(tempRoot, 'currentness.jsonl');
92
- fs.writeFileSync(file, '');
93
- const opened = require('../state/docs').openDoc(file);
91
+ const project = require('../workspace/wall').projectForRepo(repoKey);
92
+ const resourceId = project.resourceId;
93
+ const file = project.file;
94
+ projectStream.writeSnapshot(file, projectStream._test.emptySnapshot(repoKey));
94
95
  require('../state/replicas').registerReplica({
95
96
  resourceId,
96
- localResource: opened.resource,
97
+ localResource: `${projectStream.LOCAL_RESOURCE_PREFIX}${resourceId}`,
97
98
  localPath: file,
98
- contract: 'text-yjs@1',
99
+ contract: projectStream.STREAM_CONTRACT,
99
100
  schemaVersion: 1,
100
101
  authorityEpoch: 1,
101
102
  appliedVersion: 12,
@@ -113,7 +114,14 @@ test('publication requires one current applied rail position and no unresolved s
113
114
  pending: 0,
114
115
  current: true,
115
116
  });
116
- require('../state/docs').writeDocText(file, 'pending\n');
117
+ projectStream.publish(project, {
118
+ wall: 'code-project@6',
119
+ project: repoKey,
120
+ machine: 'machine-a',
121
+ mutationId: 'mut_currentness_pending_01',
122
+ publishedAt: '2026-07-26T00:00:00.000Z',
123
+ cards: [],
124
+ });
117
125
  assert.deepEqual(publicationStatus(repoKey, { appliedThrough: 12 }), {
118
126
  receivedThrough: 12,
119
127
  appliedThrough: 12,
@@ -195,10 +203,10 @@ test('receivers stay silent and only unacknowledged local parked work republishe
195
203
  assert.deepEqual(
196
204
  checkpointTest.localParkedEntries(
197
205
  { ...transition, parked: [localParked] },
198
- { parked: [peerParked, unsentParked] },
206
+ { parked: [{ ...localParked, version: 10 }, peerParked, unsentParked] },
199
207
  local,
200
208
  ),
201
- [localParked, unsentParked],
209
+ [unsentParked],
202
210
  );
203
211
  assert.deepEqual(parkedForState([localParked], [], []), [localParked]);
204
212
  assert.deepEqual(
@@ -254,59 +262,89 @@ test('card, branch, checkpoint, and parked work ride one wall mutation', () => {
254
262
  assert.equal(stamped.checkpoints[0].parked[0].oid, parkedOid);
255
263
  });
256
264
 
257
- test('two concurrent compacting publishers converge without losing either latest value', () => {
258
- const Y = require('yjs');
259
- const repoKey = 'root:compaction-collision';
260
- const line = (machine, value) => JSON.stringify({
261
- wall: 'code-project@4',
262
- project: repoKey,
263
- machine,
264
- mutationId: `mut_${machine}_${value}`,
265
- cards: [{ value }],
266
- });
267
- const baseText = [
268
- line('machine-a', 'old-a'),
269
- line('machine-b', 'old-b'),
270
- line('machine-a', 'current-a'),
271
- line('machine-b', 'current-b'),
272
- '',
273
- ].join('\n');
274
- const compacted = compactWallText(baseText, repoKey);
275
- const removedBytes = baseText.length - compacted.length;
276
- assert.ok(removedBytes > 0);
265
+ test('a new card waits for its bundle instead of publishing transport metadata twice', () => {
266
+ const oldOid = 'a'.repeat(40);
267
+ const newOid = 'b'.repeat(40);
268
+ const plan = {
269
+ card: { head: { branch: 'main', oid: oldOid, detached: false } },
270
+ branches: [{ name: 'main', oid: oldOid }],
271
+ };
272
+ const pending = {
273
+ head: { branch: 'main', oid: newOid, detached: false },
274
+ objects: { kind: 'pending' },
275
+ refs: [{ name: 'main', oid: newOid, objects: { kind: 'pending' } }],
276
+ };
277
+ const ready = {
278
+ ...pending,
279
+ objects: { kind: 'bundle' },
280
+ refs: pending.refs.map((ref) => ({ ...ref, objects: { kind: 'bundle' } })),
281
+ };
277
282
 
278
- const base = new Y.Doc();
279
- base.getText('content').insert(0, baseText);
280
- const baseUpdate = Y.encodeStateAsUpdate(base);
281
- const baseVector = Y.encodeStateVector(base);
282
- const publisherUpdate = (machine, value) => {
283
- const writer = new Y.Doc();
284
- Y.applyUpdate(writer, baseUpdate);
285
- const text = writer.getText('content');
286
- text.delete(0, removedBytes);
287
- text.insert(text.length, `${line(machine, value)}\n`);
288
- return Y.encodeStateAsUpdate(writer, baseVector);
283
+ assert.equal(wallTest.hasPendingContributionObjects(pending, plan), true);
284
+ assert.equal(wallTest.hasPendingContributionObjects(ready, plan), false);
285
+ });
286
+
287
+ test('an unnumbered branch waits for its bundle before sharing the card', () => {
288
+ const oid = 'b'.repeat(40);
289
+ const card = {
290
+ head: { branch: 'main', oid: 'a'.repeat(40) },
291
+ refs: [{ name: 'topic', oid, objects: { kind: 'pending' } }],
289
292
  };
290
- const updateA = publisherUpdate('machine-a', 'latest-a');
291
- const updateB = publisherUpdate('machine-b', 'latest-b');
292
- const replicas = [
293
- [updateA, updateB],
294
- [updateB, updateA],
295
- ].map((order) => {
296
- const replica = new Y.Doc();
297
- Y.applyUpdate(replica, baseUpdate);
298
- for (const update of order) Y.applyUpdate(replica, update);
299
- return replica.getText('content').toString();
300
- });
293
+ const plan = {
294
+ card: { head: { branch: 'main', oid: 'a'.repeat(40) } },
295
+ branches: [{ name: 'topic', oid, version: null }],
296
+ };
297
+ assert.equal(wallTest.hasPendingContributionObjects(card, plan), true);
298
+ plan.branches[0].version = 50;
299
+ assert.equal(wallTest.hasPendingContributionObjects(card, plan), false);
300
+ });
301
301
 
302
- assert.equal(replicas[0], replicas[1]);
303
- const latest = new Map();
304
- for (const raw of replicas[0].trim().split('\n')) {
305
- const entry = JSON.parse(raw);
306
- latest.set(entry.machine, entry.cards[0].value);
307
- }
308
- assert.deepEqual(Object.fromEntries(latest), {
309
- 'machine-a': 'latest-a',
310
- 'machine-b': 'latest-b',
302
+ test('transport metadata without a real card, branch, or checkpoint change is silent', () => {
303
+ const previousMutation = 'mut_previous_content';
304
+ const metadataOnly = [{
305
+ positionMutationId: previousMutation,
306
+ branches: [{ name: 'main', mutationId: previousMutation }],
307
+ checkpoints: [{ mutationId: previousMutation }],
308
+ }];
309
+ assert.equal(wallTest.cardsUseMutation(metadataOnly, 'mut_new_transport_metadata'), false);
310
+ assert.equal(wallTest.cardsUseMutation([{
311
+ ...metadataOnly[0],
312
+ checkpoints: [{ mutationId: 'mut_real_checkpoint' }],
313
+ }], 'mut_real_checkpoint'), true);
314
+ });
315
+
316
+ test('a snapshot is exactly one latest numbered record per machine', () => {
317
+ const project = 'root:snapshot';
318
+ const snapshot = projectStream._test.emptySnapshot(project);
319
+ const record = (machine, mutationId, value) => ({
320
+ wall: 'code-project@6',
321
+ project,
322
+ machine,
323
+ mutationId,
324
+ publishedAt: '2026-07-26T00:00:00.000Z',
325
+ cards: [{ value }],
311
326
  });
327
+ const afterA = projectStream._test.applyRecord(
328
+ snapshot,
329
+ record('machine-a', 'mut_snapshot_machine_a1', 'old-a'),
330
+ 1,
331
+ );
332
+ const afterB = projectStream._test.applyRecord(
333
+ afterA,
334
+ record('machine-b', 'mut_snapshot_machine_b1', 'current-b'),
335
+ 2,
336
+ );
337
+ const compacted = projectStream._test.applyRecord(
338
+ afterB,
339
+ record('machine-a', 'mut_snapshot_machine_a2', 'current-a'),
340
+ 3,
341
+ );
342
+ assert.equal(compacted.version, 3);
343
+ assert.deepEqual(
344
+ compacted.records.map((entry) => [entry.machine, entry.version, entry.cards[0].value]),
345
+ [
346
+ ['machine-a', 3, 'current-a'],
347
+ ['machine-b', 2, 'current-b'],
348
+ ],
349
+ );
312
350
  });
@@ -152,6 +152,50 @@ test('wall reduction chooses one rail winner and orders checkpoint transitions b
152
152
  ]);
153
153
  });
154
154
 
155
+ test('copies of one numbered card reduce to the copy with ready objects', () => {
156
+ const oid = 'a'.repeat(40);
157
+ const mutationId = 'mut_same_card_01';
158
+ const pending = card('/pending', oid, 7);
159
+ pending.replica = 'rpl_pending';
160
+ pending.positionMutationId = mutationId;
161
+ pending.objects = { kind: 'pending' };
162
+ pending.branches = [{
163
+ name: 'feature',
164
+ oid,
165
+ mutationId,
166
+ version: 7,
167
+ objects: { kind: 'pending' },
168
+ }];
169
+
170
+ const ready = card('/ready', oid, 7);
171
+ ready.replica = 'rpl_ready';
172
+ ready.positionMutationId = mutationId;
173
+ ready.objects = { kind: 'bundle', checksum: 'b'.repeat(64) };
174
+ ready.branches = [{
175
+ name: 'feature',
176
+ oid,
177
+ mutationId,
178
+ version: 7,
179
+ objects: { kind: 'bundle', checksum: 'b'.repeat(64) },
180
+ }];
181
+
182
+ for (const entries of [
183
+ [
184
+ { machine: 'machine-pending', cards: [pending] },
185
+ { machine: 'machine-ready', cards: [ready] },
186
+ ],
187
+ [
188
+ { machine: 'machine-ready', cards: [ready] },
189
+ { machine: 'machine-pending', cards: [pending] },
190
+ ],
191
+ ]) {
192
+ const plan = reduceWall(entries).get('root:test');
193
+ assert.equal(plan.card.positionVersion, 7);
194
+ assert.equal(plan.card.objects.kind, 'bundle');
195
+ assert.equal(plan.branches.find((ref) => ref.name === 'feature').objects.kind, 'bundle');
196
+ }
197
+ });
198
+
155
199
  test('a checkpoint base replaces covered history and keeps only newer mutations', async () => {
156
200
  const { repo, oid } = await makeRepo('checkpoint-base');
157
201
  const patchA = await patchFor(repo, { 'a.txt': 'a1\n' });
@@ -511,8 +555,8 @@ test('a mode-only checkpoint makes the peer working file executable', async () =
511
555
  assert.equal(await git(repo, 'diff', '--cached', '--binary', '--full-index'), '');
512
556
  });
513
557
 
514
- test('once local work is on the rail, a checkpoint conflict materializes exactly without echoing', async () => {
515
- const { repo, oid } = await makeRepo('materialize-conflict');
558
+ test('once local work is on the rail, a later checkpoint replaces it exactly without echoing', async () => {
559
+ const { repo, oid } = await makeRepo('materialize-later-wins');
516
560
  const localPatch = await patchFor(repo, { 'a.txt': 'local answer\n' });
517
561
  const remotePatch = await patchFor(repo, { 'a.txt': 'remote answer\n' });
518
562
  const localTransition = transition('machine-local', 2, oid, '', localPatch);
@@ -534,16 +578,44 @@ test('once local work is on the rail, a checkpoint conflict materializes exactly
534
578
  }), true);
535
579
 
536
580
  const outcome = await materializeRepo(plan.card, plan);
537
- assert.equal(outcome.state, 'conflict');
581
+ assert.equal(outcome.state, 'in-sync');
538
582
  const first = fs.readFileSync(path.join(repo, 'a.txt'), 'utf8');
539
- assert.match(first, /local answer/);
540
- assert.match(first, /remote answer/);
541
- assert.match(first, /<<<<<<< current/);
583
+ assert.equal(first, 'remote answer\n');
542
584
 
543
585
  await materializeRepo(plan.card, plan);
544
586
  assert.equal(fs.readFileSync(path.join(repo, 'a.txt'), 'utf8'), first);
545
587
  });
546
588
 
589
+ test('a snapshot cannot hide that local work was already numbered', async () => {
590
+ const { repo, oid } = await makeRepo('materialize-snapshot-ack');
591
+ const localPatch = await patchFor(repo, { 'a.txt': 'local answer\n' });
592
+ const remotePatch = await patchFor(repo, { 'a.txt': 'remote answer\n' });
593
+ const localTransition = transition('machine-local', 2, oid, '', localPatch);
594
+ const plan = {
595
+ repoKey: 'root:test',
596
+ card: card(repo, oid, 1),
597
+ checkpointBase: {
598
+ through: 2,
599
+ baseOid: oid,
600
+ branch: 'main',
601
+ value: localTransition.to,
602
+ },
603
+ transitions: [transition('machine-remote', 3, oid, localPatch, remotePatch)],
604
+ };
605
+ const captured = {
606
+ baseOid: oid,
607
+ indexDiff: localPatch,
608
+ worktreeDiff: '',
609
+ };
610
+
611
+ assert.equal(captureIsOnRail(plan, captured, 'machine-local'), false);
612
+ assert.equal(captureIsOnRail(plan, captured, 'machine-local', {
613
+ ...localTransition,
614
+ mutationId: 'mut_local_numbered',
615
+ version: 2,
616
+ }), true);
617
+ });
618
+
547
619
  test('a writer publishing again cannot erase its earlier uncommitted work', async () => {
548
620
  const { repo, oid } = await makeRepo('checkpoint-history');
549
621
  const patchA = await patchFor(repo, { 'a.txt': 'a1\n' });
@@ -578,13 +650,12 @@ test('a writer publishing again cannot erase its earlier uncommitted work', asyn
578
650
  assert.equal(fs.readFileSync(path.join(repo, 'c.txt'), 'utf8'), 'c1\n');
579
651
  });
580
652
 
581
- test('a losing commit is parked, the winning card lands exactly, and losing work becomes uncommitted', async () => {
653
+ test('a losing commit is archived, the winning card lands exactly, and later dirty work stays', async () => {
582
654
  const { repo, oid: baseOid } = await makeRepo('simultaneous-commit');
583
655
  fs.writeFileSync(path.join(repo, 'local-commit.txt'), 'losing commit work\n');
584
656
  await git(repo, 'add', '.');
585
657
  await git(repo, 'commit', '-m', 'local losing commit');
586
658
  const losingOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
587
- fs.writeFileSync(path.join(repo, 'local-dirty.txt'), 'also uncommitted\n');
588
659
 
589
660
  await git(repo, 'switch', '--detach', baseOid);
590
661
  fs.writeFileSync(path.join(repo, 'winner.txt'), 'winning commit work\n');
@@ -592,6 +663,7 @@ test('a losing commit is parked, the winning card lands exactly, and losing work
592
663
  await git(repo, 'commit', '-m', 'remote winning commit');
593
664
  const winningOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
594
665
  await git(repo, 'switch', '-C', 'main', losingOid);
666
+ fs.writeFileSync(path.join(repo, 'local-dirty.txt'), 'also uncommitted\n');
595
667
 
596
668
  const localCard = card(repo, losingOid, 1);
597
669
  const winningCard = {
@@ -611,7 +683,7 @@ test('a losing commit is parked, the winning card lands exactly, and losing work
611
683
  assert.equal((await git(repo, 'rev-parse', 'HEAD')).trim(), winningOid);
612
684
  assert.equal((await git(repo, 'rev-parse', `refs/amalgm/parked/${losingOid}`)).trim(), losingOid);
613
685
  assert.equal(fs.readFileSync(path.join(repo, 'winner.txt'), 'utf8'), 'winning commit work\n');
614
- assert.equal(fs.readFileSync(path.join(repo, 'local-commit.txt'), 'utf8'), 'losing commit work\n');
686
+ assert.equal(fs.existsSync(path.join(repo, 'local-commit.txt')), false);
615
687
  assert.equal(fs.readFileSync(path.join(repo, 'local-dirty.txt'), 'utf8'), 'also uncommitted\n');
616
688
  assert.equal((await git(repo, 'status', '--porcelain')).trim().length > 0, true);
617
689
  assert.deepEqual(readRepoState('root:test').parked.map((entry) => entry.oid), [losingOid]);
@@ -626,26 +698,28 @@ test('a losing commit is parked, the winning card lands exactly, and losing work
626
698
  transitions: [],
627
699
  parked: sharedParked,
628
700
  });
701
+ assert.equal(fs.existsSync(path.join(repo, 'local-commit.txt')), false);
702
+ assert.equal(fs.readFileSync(path.join(repo, 'local-dirty.txt'), 'utf8'), 'also uncommitted\n');
629
703
 
630
704
  await git(repo, 'add', '.');
631
- await git(repo, 'commit', '-m', 'fold parked work');
632
- const foldedOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
633
- const foldedCard = card(repo, foldedOid, 3);
634
- const folded = await materializeRepo(foldedCard, {
705
+ await git(repo, 'commit', '-m', 'commit later dirty work');
706
+ const nextOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
707
+ const nextCard = card(repo, nextOid, 3);
708
+ const advanced = await materializeRepo(nextCard, {
635
709
  repoKey: 'root:test',
636
- card: foldedCard,
710
+ card: nextCard,
637
711
  transitions: [],
638
712
  parked: [],
639
713
  });
640
714
 
641
- assert.equal(folded.state, 'in-sync');
715
+ assert.equal(advanced.state, 'in-sync');
642
716
  assert.deepEqual(readRepoState('root:test').parked, []);
643
717
  await assert.rejects(
644
718
  git(repo, 'rev-parse', '--verify', `refs/amalgm/parked/${losingOid}`),
645
719
  );
646
720
  });
647
721
 
648
- test('rail-parked peer commits become one staged checkpoint on every machine', async () => {
722
+ test('rail-parked peer commits are archived without changing the winning card tree', async () => {
649
723
  const { repo, oid: baseOid } = await makeRepo('peer-parked-commits');
650
724
 
651
725
  await git(repo, 'switch', '-c', 'winner');
@@ -680,13 +754,61 @@ test('rail-parked peer commits become one staged checkpoint on every machine', a
680
754
 
681
755
  assert.equal(outcome.state, 'in-sync');
682
756
  assert.equal((await git(repo, 'rev-parse', 'HEAD')).trim(), winningOid);
757
+ assert.equal((await git(repo, 'diff', '--cached', '--name-only')).trim(), '');
758
+ assert.equal((await git(repo, 'diff', '--name-only')).trim(), '');
759
+ assert.equal(fs.existsSync(path.join(repo, 'loser-a.txt')), false);
760
+ assert.equal(fs.existsSync(path.join(repo, 'loser-b.txt')), false);
761
+ assert.equal((await git(repo, 'rev-parse', `refs/amalgm/parked/${losingAOid}`)).trim(), losingAOid);
762
+ assert.equal((await git(repo, 'rev-parse', `refs/amalgm/parked/${losingBOid}`)).trim(), losingBOid);
763
+ });
764
+
765
+ test('a local losing commit stays pending while a peer parked bundle is missing', async () => {
766
+ const { repo, oid: baseOid } = await makeRepo('parked-while-peer-bundle-waits');
767
+
768
+ await git(repo, 'switch', '-c', 'winner');
769
+ fs.writeFileSync(path.join(repo, 'winner.txt'), 'winning card\n');
770
+ await git(repo, 'add', '.');
771
+ await git(repo, 'commit', '-m', 'winning card');
772
+ const winningOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
773
+
774
+ await git(repo, 'switch', '-C', 'main', baseOid);
775
+ fs.writeFileSync(path.join(repo, 'local-loser.txt'), 'local losing commit\n');
776
+ await git(repo, 'add', '.');
777
+ await git(repo, 'commit', '-m', 'local losing commit');
778
+ const losingOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
779
+
780
+ const localCard = card(repo, losingOid, 1);
781
+ const winningCard = {
782
+ ...localCard,
783
+ head: { branch: 'main', oid: winningOid, detached: false },
784
+ positionVersion: 2,
785
+ positionMutationId: 'mut_card_winner_waiting',
786
+ };
787
+ const peerOid = 'f'.repeat(40);
788
+ const plan = {
789
+ repoKey: 'root:test',
790
+ card: winningCard,
791
+ transitions: [],
792
+ parked: [{ oid: peerOid, version: 3 }],
793
+ };
794
+
795
+ const first = await materializeRepo(localCard, plan, 3);
796
+ assert.equal(first.state, 'waiting');
797
+ assert.equal(first.reason, 'parked-objects-missing');
798
+ assert.equal((await git(repo, 'rev-parse', 'HEAD')).trim(), winningOid);
799
+ assert.equal((await git(repo, 'rev-parse', `refs/amalgm/parked/${losingOid}`)).trim(), losingOid);
683
800
  assert.deepEqual(
684
- (await git(repo, 'diff', '--cached', '--name-only')).trim().split('\n'),
685
- ['loser-a.txt', 'loser-b.txt'],
801
+ readRepoState('root:test').parked.map((entry) => entry.oid).sort(),
802
+ [losingOid, peerOid].sort(),
803
+ );
804
+
805
+ const second = await materializeRepo(card(repo, winningOid, 2), plan, 3);
806
+ assert.equal(second.state, 'waiting');
807
+ assert.equal((await git(repo, 'rev-parse', `refs/amalgm/parked/${losingOid}`)).trim(), losingOid);
808
+ assert.deepEqual(
809
+ readRepoState('root:test').parked.map((entry) => entry.oid).sort(),
810
+ [losingOid, peerOid].sort(),
686
811
  );
687
- assert.equal((await git(repo, 'diff', '--name-only')).trim(), '');
688
- assert.equal(fs.readFileSync(path.join(repo, 'loser-a.txt'), 'utf8'), 'first losing commit\n');
689
- assert.equal(fs.readFileSync(path.join(repo, 'loser-b.txt'), 'utf8'), 'second losing commit\n');
690
812
  });
691
813
 
692
814
  test('a local commit made after the verified card snapshot is never parked or overwritten', async () => {
@@ -800,7 +922,7 @@ test('an index advance preserves unrelated work staged by another writer', async
800
922
  assert.deepEqual(staged.trim().split('\n').sort(), ['a.txt', 'b.txt']);
801
923
  });
802
924
 
803
- test('a new card folds checkpoint work already on the rail instead of replaying it as local work', async () => {
925
+ test('a new card includes checkpoint work already on the rail instead of replaying it as local work', async () => {
804
926
  const { repo, oid: baseOid } = await makeRepo('card-folds-shared-checkpoint');
805
927
  const sharedPatch = await patchFor(repo, { 'a.txt': 'shared checkpoint\n' });
806
928
 
@@ -835,7 +957,7 @@ test('a new card folds checkpoint work already on the rail instead of replaying
835
957
  assert.equal((await git(repo, 'status', '--porcelain')).trim(), '');
836
958
  });
837
959
 
838
- test('checkpoint then commit folds through the same card movement and unrelated local work stays', async () => {
960
+ test('checkpoint then commit advances through the same card movement and unrelated local work stays', async () => {
839
961
  const { repo, oid: baseOid } = await makeRepo('checkpoint-commit-fold');
840
962
  await git(repo, 'switch', '-c', 'target');
841
963
  fs.writeFileSync(path.join(repo, 'shared.txt'), 'already checkpointed\n');
@@ -903,6 +1025,75 @@ test('the materializer creates, moves, and deletes branch refs without checking
903
1025
  assert.equal((await git(repo, 'branch', '--show-current')).trim(), 'main');
904
1026
  });
905
1027
 
1028
+ test('a branch event is not applied until its commit is available', async () => {
1029
+ const { repo, oid } = await makeRepo('branch-event-waits');
1030
+ const peer = path.join(tempRoot, 'branch-event-waits-peer');
1031
+ await git(tempRoot, 'clone', '--quiet', repo, peer);
1032
+ await git(peer, 'switch', '-c', 'feature');
1033
+ fs.writeFileSync(path.join(peer, 'feature.txt'), 'feature\n');
1034
+ await git(peer, 'add', '.');
1035
+ await git(peer, 'commit', '-m', 'feature');
1036
+ const featureOid = (await git(peer, 'rev-parse', 'HEAD')).trim();
1037
+
1038
+ const repoKey = 'root:branch-event-waits';
1039
+ const localCard = card(repo, oid, 1);
1040
+ const plan = {
1041
+ repoKey,
1042
+ card: localCard,
1043
+ transitions: [],
1044
+ parked: [],
1045
+ branches: [
1046
+ { name: 'main', oid, version: 1 },
1047
+ { name: 'feature', oid: featureOid, version: 2 },
1048
+ ],
1049
+ };
1050
+
1051
+ const waiting = await materializeRepo(localCard, plan, 2);
1052
+ assert.deepEqual(waiting, {
1053
+ state: 'waiting',
1054
+ reason: 'branch-objects-missing',
1055
+ });
1056
+ assert.equal(readRepoState(repoKey), null);
1057
+ await assert.rejects(() => git(repo, 'rev-parse', 'refs/heads/feature'));
1058
+
1059
+ await git(repo, 'fetch', '--quiet', peer, 'feature:refs/amalgm-test/feature');
1060
+ const applied = await materializeRepo(localCard, plan, 2);
1061
+ assert.equal(applied.state, 'in-sync');
1062
+ assert.equal((await git(repo, 'rev-parse', 'refs/heads/feature')).trim(), featureOid);
1063
+ assert.equal(readRepoState(repoKey).appliedThrough, 2);
1064
+ });
1065
+
1066
+ test('an unpublished local branch delete is not restored while catching up', async () => {
1067
+ const { repo, oid } = await makeRepo('pending-branch-delete');
1068
+ await git(repo, 'switch', '-c', 'feature');
1069
+ fs.writeFileSync(path.join(repo, 'feature.txt'), 'feature\n');
1070
+ await git(repo, 'add', '.');
1071
+ await git(repo, 'commit', '-m', 'feature');
1072
+ const featureOid = (await git(repo, 'rev-parse', 'HEAD')).trim();
1073
+ await git(repo, 'switch', 'main');
1074
+ await git(repo, 'branch', '-D', 'feature');
1075
+
1076
+ const localCard = card(repo, oid, 1);
1077
+ const plan = {
1078
+ repoKey: 'root:pending-branch-delete',
1079
+ card: localCard,
1080
+ transitions: [],
1081
+ parked: [],
1082
+ branches: [
1083
+ { name: 'main', oid, version: 1 },
1084
+ { name: 'feature', oid: featureOid, version: 2 },
1085
+ ],
1086
+ };
1087
+ await materializeRepo(localCard, plan);
1088
+ assert.equal((await git(repo, 'rev-parse', 'refs/heads/feature')).trim(), featureOid);
1089
+
1090
+ await git(repo, 'branch', '-D', 'feature');
1091
+ await materializeRepo(localCard, plan);
1092
+
1093
+ await assert.rejects(() => git(repo, 'rev-parse', 'refs/heads/feature'));
1094
+ assert.equal((await git(repo, 'branch', '--show-current')).trim(), 'main');
1095
+ });
1096
+
906
1097
  test('a card branch switch follows branch and OID together and leaves the old branch unchanged', async () => {
907
1098
  const { repo, oid: mainOid } = await makeRepo('card-branch-switch');
908
1099
  await git(repo, 'checkout', '-b', 'feature');