amalgm 0.1.180 → 0.1.182

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.180",
3
+ "version": "0.1.182",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -164,7 +164,7 @@ test('checkpoint captures are local cache; their operations travel on the one wa
164
164
  assert.equal(first, checkpointFile('m1', 'https://github.com/a/b'));
165
165
  assert.notEqual(first, checkpointFile('m2', 'https://github.com/a/b'));
166
166
  assert.notEqual(first, checkpointFile('m1', 'https://github.com/a/c'));
167
- assert.match(first, /repo-checkpoints-v5/);
167
+ assert.match(first, /repo-checkpoints-v6/);
168
168
  });
169
169
 
170
170
  test('an uploading checkpoint payload is not published as a transition', () => {
@@ -21,8 +21,9 @@ fs.writeFileSync(
21
21
  );
22
22
 
23
23
  const { closeLocalDb } = require('../state/db');
24
- const { revisionOf } = require('../workspace/checkpoint');
24
+ const { CHECKPOINT_CONTRACT, revisionOf } = require('../workspace/checkpoint');
25
25
  const {
26
+ captureIsOnRail,
26
27
  hydrateRepo,
27
28
  materializeRepo,
28
29
  readRepoState,
@@ -72,7 +73,7 @@ function value(baseOid, diff) {
72
73
 
73
74
  function transition(machine, version, baseOid, from, to) {
74
75
  return {
75
- checkpoint: 'repo-checkpoint@5',
76
+ checkpoint: CHECKPOINT_CONTRACT,
76
77
  machine,
77
78
  repoKey: 'root:test',
78
79
  branch: 'main',
@@ -129,6 +130,18 @@ test('a stale lower rail card cannot roll back a newer branch and commit', () =>
129
130
  assert.equal(plan.card.positionVersion, 12);
130
131
  });
131
132
 
133
+ test('checkpoint reduction reads only the current rail contract', () => {
134
+ const current = card('/current-contract', 'a'.repeat(40), 1);
135
+ const stale = transition('machine-local', 2, current.head.oid, '', 'stale');
136
+ stale.checkpoint = 'repo-checkpoint@5';
137
+ current.checkpoints = [
138
+ stale,
139
+ transition('machine-local', 3, current.head.oid, '', 'current'),
140
+ ];
141
+ const plan = reduceWall([{ machine: 'machine-local', cards: [current] }]).get('root:test');
142
+ assert.deepEqual(plan.transitions.map((item) => item.version), [3]);
143
+ });
144
+
132
145
  test('an old pending payload does not block the later complete checkpoint transition', async () => {
133
146
  const { repo, oid } = await makeRepo('pending-payload');
134
147
  const diff = await patchFor(repo, { 'a.txt': 'a1\n' });
@@ -296,6 +309,39 @@ test('one materializer converges to the reduced checkpoint and later removes a r
296
309
  assert.equal(fs.readFileSync(path.join(repo, 'b.txt'), 'utf8'), 'b1\n');
297
310
  });
298
311
 
312
+ test('once local work is on the rail, a checkpoint conflict materializes exactly without echoing', async () => {
313
+ const { repo, oid } = await makeRepo('materialize-conflict');
314
+ const localPatch = await patchFor(repo, { 'a.txt': 'local answer\n' });
315
+ const remotePatch = await patchFor(repo, { 'a.txt': 'remote answer\n' });
316
+ const localTransition = transition('machine-local', 2, oid, '', localPatch);
317
+ const plan = {
318
+ repoKey: 'root:test',
319
+ card: card(repo, oid, 1),
320
+ transitions: [
321
+ localTransition,
322
+ transition('machine-remote', 3, oid, '', remotePatch),
323
+ ],
324
+ };
325
+
326
+ fs.writeFileSync(path.join(repo, 'a.txt'), 'local answer\n');
327
+ await git(repo, 'add', 'a.txt');
328
+ assert.equal(captureIsOnRail(plan, {
329
+ baseOid: oid,
330
+ indexDiff: localPatch,
331
+ worktreeDiff: '',
332
+ }), true);
333
+
334
+ const outcome = await materializeRepo(plan.card, plan);
335
+ assert.equal(outcome.state, 'conflict');
336
+ const first = fs.readFileSync(path.join(repo, 'a.txt'), 'utf8');
337
+ assert.match(first, /local answer/);
338
+ assert.match(first, /remote answer/);
339
+ assert.match(first, /<<<<<<< current/);
340
+
341
+ await materializeRepo(plan.card, plan);
342
+ assert.equal(fs.readFileSync(path.join(repo, 'a.txt'), 'utf8'), first);
343
+ });
344
+
299
345
  test('a writer publishing again cannot erase its earlier uncommitted work', async () => {
300
346
  const { repo, oid } = await makeRepo('checkpoint-history');
301
347
  const patchA = await patchFor(repo, { 'a.txt': 'a1\n' });
@@ -155,7 +155,7 @@ test('checkpoint transitions append until the next card and carry their official
155
155
  const file = checkpointFile('machine-a', repoKey);
156
156
  fs.mkdirSync(path.dirname(file), { recursive: true });
157
157
  const firstCheckpoint = {
158
- checkpoint: 'repo-checkpoint@5',
158
+ checkpoint: 'repo-checkpoint@6',
159
159
  machine: 'machine-a',
160
160
  repoKey,
161
161
  branch: card.head.branch,
@@ -25,7 +25,7 @@ const { machineId } = require('./identity');
25
25
  const { listRepoCards } = require('./cards');
26
26
  const { captureCheckpoint } = require('./git');
27
27
 
28
- const CHECKPOINT_CONTRACT = 'repo-checkpoint@5';
28
+ const CHECKPOINT_CONTRACT = 'repo-checkpoint@6';
29
29
  const MAX_INLINE_DIFF_BYTES = 512 * 1024;
30
30
  const PUBLISH_QUIET_MS = 25;
31
31
  const RECONCILE_INTERVAL_MS = 30_000;
@@ -54,7 +54,7 @@ function checkpointFile(machine, repoKey) {
54
54
  .update(`${CHECKPOINT_CONTRACT}|${machine}|${repoKey}`)
55
55
  .digest('hex')
56
56
  .slice(0, 24);
57
- return path.join(AMALGM_DIR, 'state', 'repo-checkpoints-v5', `${digest}.json`);
57
+ return path.join(AMALGM_DIR, 'state', 'repo-checkpoints-v6', `${digest}.json`);
58
58
  }
59
59
 
60
60
  function revisionOf(baseOid, indexDiff, worktreeDiff = '') {
@@ -28,10 +28,12 @@ const {
28
28
  operationState,
29
29
  } = require('./git');
30
30
  const {
31
+ CHECKPOINT_CONTRACT,
31
32
  readCheckpointValue,
32
33
  repoKeyOfCard,
33
34
  revisionOf,
34
35
  } = require('./checkpoint');
36
+ const { machineId } = require('./identity');
35
37
  const merge = require('./merge');
36
38
  const transition = require('./transition');
37
39
 
@@ -105,7 +107,8 @@ function reduceWall(entries) {
105
107
  }
106
108
  for (const checkpoint of card.checkpoints || []) {
107
109
  if (
108
- validVersion(checkpoint.version)
110
+ checkpoint.checkpoint === CHECKPOINT_CONTRACT
111
+ && validVersion(checkpoint.version)
109
112
  && checkpoint.machine === wallEntry.machine
110
113
  ) {
111
114
  repo.transitions.push(checkpoint);
@@ -416,6 +419,25 @@ async function reduceCheckpoint(localCard, plan) {
416
419
  };
417
420
  }
418
421
 
422
+ function captureIsOnRail(plan, captured, localMachine = machineId()) {
423
+ if (
424
+ !captured
425
+ || captured.indexDiff === null
426
+ || captured.baseOid !== plan.card.head.oid
427
+ ) return false;
428
+ const latest = [...plan.transitions]
429
+ .reverse()
430
+ .find((operation) => operation.machine === localMachine);
431
+ return Boolean(
432
+ latest
433
+ && latest.to?.revision === revisionOf(
434
+ captured.baseOid,
435
+ captured.indexDiff,
436
+ captured.worktreeDiff,
437
+ ),
438
+ );
439
+ }
440
+
419
441
  async function materializeRepo(localCardInput, plan) {
420
442
  const refreshed = await refreshRepoCard(localCardInput.replica) || localCardInput;
421
443
  const cardResult = await moveToWinningCard(refreshed, plan.card);
@@ -436,16 +458,28 @@ async function materializeRepo(localCardInput, plan) {
436
458
  if (reduced.state !== 'ready') return reduced;
437
459
 
438
460
  const baseTree = await merge.treeOf(localCard.path, plan.card.head.oid);
439
- const heldWorktree = previous?.baseOid === plan.card.head.oid
440
- && previous.heldTree
441
- && await merge.hasTree(localCard.path, previous.heldTree)
442
- ? previous.heldTree
443
- : baseTree;
444
- const heldIndex = previous?.baseOid === plan.card.head.oid
445
- && previous.heldIndexTree
446
- && await merge.hasTree(localCard.path, previous.heldIndexTree)
447
- ? previous.heldIndexTree
448
- : baseTree;
461
+ const captured = await captureCheckpoint(localCard.path);
462
+ const represented = captureIsOnRail(plan, captured);
463
+ const capturedIndex = represented
464
+ ? await merge.treeOfPatch(localCard.path, plan.card.head.oid, captured.indexDiff)
465
+ : null;
466
+ const capturedWorktree = represented
467
+ ? await merge.treeOfPatch(localCard.path, capturedIndex, captured.worktreeDiff)
468
+ : null;
469
+ const heldWorktree = capturedWorktree || (
470
+ previous?.baseOid === plan.card.head.oid
471
+ && previous.heldTree
472
+ && await merge.hasTree(localCard.path, previous.heldTree)
473
+ ? previous.heldTree
474
+ : baseTree
475
+ );
476
+ const heldIndex = capturedIndex || (
477
+ previous?.baseOid === plan.card.head.oid
478
+ && previous.heldIndexTree
479
+ && await merge.hasTree(localCard.path, previous.heldIndexTree)
480
+ ? previous.heldIndexTree
481
+ : baseTree
482
+ );
449
483
  const currentIndex = await merge.indexTree(localCard.path);
450
484
  const reducedLocalIndex = await merge.reduceTree(
451
485
  localCard.path,
@@ -642,6 +676,7 @@ function reducerStats() {
642
676
  }
643
677
 
644
678
  module.exports = {
679
+ captureIsOnRail,
645
680
  hydrateRepo,
646
681
  materializeRepo,
647
682
  readRepoState,