amalgm 0.1.189 → 0.1.191

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.189",
3
+ "version": "0.1.191",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -31,12 +31,14 @@ const { refreshRepoCards } = require('../workspace/cards');
31
31
  const { checkpointFile, repoKeyOfCard } = require('../workspace/checkpoint');
32
32
  const {
33
33
  bindProject,
34
+ hasLocalContribution,
34
35
  isCommittedCard,
35
36
  projectForRepo,
36
37
  projectResourceId,
37
38
  publishedCardFor,
38
39
  publishWall,
39
40
  readWall,
41
+ stampCards,
40
42
  wallStats,
41
43
  } = require('../workspace/wall');
42
44
 
@@ -71,6 +73,115 @@ test('no commit means no shared card', () => {
71
73
  assert.equal(isCommittedCard({ head: { branch: 'main', oid: 'a'.repeat(40) } }), true);
72
74
  });
73
75
 
76
+ test('only a value different from both the materialized baseline and the rail is new work', () => {
77
+ const oidA = 'a'.repeat(40);
78
+ const oidB = 'b'.repeat(40);
79
+ const oidC = 'c'.repeat(40);
80
+ const local = {
81
+ head: { branch: 'main', oid: oidA, detached: false },
82
+ refs: [{ name: 'main', oid: oidA }],
83
+ };
84
+ const plan = {
85
+ card: { head: { branch: 'main', oid: oidA, detached: false } },
86
+ branches: [
87
+ { name: 'main', oid: oidA },
88
+ { name: 'feature', oid: oidB },
89
+ ],
90
+ transitions: [],
91
+ };
92
+ const baseline = {
93
+ branch: 'main',
94
+ baseOid: oidA,
95
+ branches: [{ name: 'main', oid: oidA }],
96
+ };
97
+
98
+ assert.equal(hasLocalContribution(local, plan, baseline), false);
99
+ assert.equal(hasLocalContribution({
100
+ ...local,
101
+ refs: [...local.refs, { name: 'feature', oid: oidB }],
102
+ }, plan, baseline), false);
103
+ assert.equal(hasLocalContribution({
104
+ ...local,
105
+ head: { branch: 'main', oid: oidC, detached: false },
106
+ refs: [{ name: 'main', oid: oidC }],
107
+ }, plan, baseline), true);
108
+ assert.equal(hasLocalContribution(local, plan, {
109
+ ...baseline,
110
+ branches: [...baseline.branches, { name: 'feature', oid: oidB }],
111
+ }), true);
112
+
113
+ const remoteAdvance = {
114
+ ...plan,
115
+ card: { head: { branch: 'main', oid: oidC, detached: false } },
116
+ branches: [{ name: 'main', oid: oidC }],
117
+ };
118
+ assert.equal(hasLocalContribution(local, remoteAdvance, baseline), false);
119
+ assert.equal(hasLocalContribution(local, plan, baseline, {
120
+ checkpoint: 'repo-checkpoint@7',
121
+ machine: 'machine-a',
122
+ branch: 'main',
123
+ baseOid: oidA,
124
+ from: { revision: null, indexDiff: '', worktreeDiff: '' },
125
+ to: { revision: 'local-work', indexDiff: '', worktreeDiff: 'patch' },
126
+ parked: [],
127
+ }), true);
128
+ });
129
+
130
+ test('checkpoint work on the materialized baseline keeps the baseline card and branch numbers', () => {
131
+ const baselineOid = 'a'.repeat(40);
132
+ const railOid = 'b'.repeat(40);
133
+ const card = {
134
+ card: 'repo-card@2',
135
+ machine: 'machine-a',
136
+ replica: 'rpl_baseline',
137
+ kind: 'primary',
138
+ head: { branch: 'main', oid: baselineOid, detached: false },
139
+ refs: [{ name: 'main', oid: baselineOid }],
140
+ health: 'healthy',
141
+ };
142
+ const stamped = stampCards(
143
+ [card],
144
+ [{
145
+ ...card,
146
+ head: { branch: 'old', oid: 'c'.repeat(40), detached: false },
147
+ positionMutationId: 'mut_stale_card_01',
148
+ positionVersion: 8,
149
+ branches: [],
150
+ }],
151
+ {
152
+ card: {
153
+ ...card,
154
+ head: { branch: 'main', oid: railOid, detached: false },
155
+ positionMutationId: 'mut_winning_card1',
156
+ positionVersion: 12,
157
+ },
158
+ branches: [{
159
+ name: 'main',
160
+ oid: railOid,
161
+ mutationId: 'mut_winning_ref01',
162
+ version: 12,
163
+ }],
164
+ },
165
+ {
166
+ branch: 'main',
167
+ baseOid: baselineOid,
168
+ cardMutationId: 'mut_baseline_card',
169
+ cardVersion: 9,
170
+ branches: [{
171
+ name: 'main',
172
+ oid: baselineOid,
173
+ mutationId: 'mut_baseline_ref1',
174
+ version: 9,
175
+ }],
176
+ },
177
+ 'mut_checkpoint_only',
178
+ { repoKey: 'root:baseline-stamp', resourceId: 'shr_baseline_stamp_test' },
179
+ )[0];
180
+
181
+ assert.equal(stamped.positionMutationId, 'mut_baseline_card');
182
+ assert.equal(stamped.branches[0].mutationId, 'mut_baseline_ref1');
183
+ });
184
+
74
185
  test('a collaborator binds the same repo to the owner project, not a parallel project', () => {
75
186
  const shared = projectResourceId('repo-shared', '22222222-2222-4222-8222-222222222222');
76
187
  const localPath = path.join(tempRoot, 'projects/shared');
@@ -129,7 +240,7 @@ test('a repo project carries exactly one line per machine, replaced in place', a
129
240
  });
130
241
  const sealed = publishWall(repoKey);
131
242
  assert.equal(sealed.published, false);
132
- assert.equal(sealed.reason, 'unchanged');
243
+ assert.equal(sealed.reason, 'shared-state-covered');
133
244
  const sealedCard = readWall(repoKey)[0].cards[0];
134
245
  assert.equal(sealedCard.positionMutationId, first.mutationId);
135
246
  assert.equal(sealedCard.positionVersion, 1);
@@ -137,7 +248,7 @@ test('a repo project carries exactly one line per machine, replaced in place', a
137
248
 
138
249
  const again = publishWall(repoKey);
139
250
  assert.equal(again.published, false);
140
- assert.equal(again.reason, 'unchanged');
251
+ assert.equal(again.reason, 'shared-state-covered');
141
252
 
142
253
  fs.writeFileSync(path.join(repo, 'new.txt'), 'x\n');
143
254
  await refreshRepoCards();
@@ -192,6 +303,34 @@ test('checkpoint transitions append until the next card and carry their official
192
303
  assert.deepEqual(history.map((entry) => entry.mutationId), [first.mutationId, second.mutationId]);
193
304
  });
194
305
 
306
+ test('side branches keep one rail operation while the checked-out card advances', async () => {
307
+ const repo = path.join(tempRoot, 'projects/app');
308
+ const before = readWall(repoKey)[0].cards[0];
309
+ const branch = 'storm/persistent-ref';
310
+
311
+ await run(repo, 'branch', branch);
312
+ await refreshRepoCards();
313
+ const created = publishWall(repoKey);
314
+ assert.equal(created.published, true);
315
+ let card = readWall(repoKey)[0].cards[0];
316
+ const ref = card.branches.find((entry) => entry.name === branch);
317
+ assert.equal(card.positionMutationId, before.positionMutationId);
318
+ assert.equal(ref.mutationId, created.mutationId);
319
+
320
+ acknowledgeCloudMutation(project.resourceId, created.mutationId, {
321
+ version: 3,
322
+ committedAt: '2026-07-23T00:00:04.000Z',
323
+ });
324
+ await run(repo, 'add', 'new.txt');
325
+ await run(repo, 'commit', '-m', 'advance without moving side branch');
326
+ await refreshRepoCards();
327
+ const advanced = publishWall(repoKey);
328
+ assert.equal(advanced.published, true);
329
+ card = readWall(repoKey)[0].cards[0];
330
+ assert.equal(card.positionMutationId, advanced.mutationId);
331
+ assert.equal(card.branches.find((entry) => entry.name === branch).mutationId, created.mutationId);
332
+ });
333
+
195
334
  test('a peer machine line is preserved byte-for-byte through local publishes', async () => {
196
335
  const peerLine = JSON.stringify({
197
336
  wall: 'code-project@3',
@@ -197,17 +197,9 @@ function publishedCardFor(card) {
197
197
  }
198
198
 
199
199
  function samePosition(left, right) {
200
- const sameHead = left?.head?.branch === right?.head?.branch
200
+ return left?.head?.branch === right?.head?.branch
201
201
  && left?.head?.oid === right?.head?.oid
202
202
  && Boolean(left?.head?.detached) === Boolean(right?.head?.detached);
203
- if (!sameHead) return false;
204
- const previousRefs = new Map((left?.branches || []).map((ref) => [ref.name, ref.oid ?? null]));
205
- const currentRefs = new Map((right?.refs || []).map((ref) => [ref.name, ref.oid]));
206
- const names = new Set([...previousRefs.keys(), ...currentRefs.keys()]);
207
- for (const name of names) {
208
- if ((previousRefs.get(name) ?? null) !== (currentRefs.get(name) ?? null)) return false;
209
- }
210
- return true;
211
203
  }
212
204
 
213
205
  function checkpointIdentity(checkpoint) {
@@ -221,6 +213,59 @@ function checkpointIdentity(checkpoint) {
221
213
  });
222
214
  }
223
215
 
216
+ function oidMap(refs) {
217
+ return new Map((refs || []).map((ref) => [ref.name, ref.oid ?? null]));
218
+ }
219
+
220
+ /**
221
+ * Publishing is a transition from the last shared state this checkout
222
+ * materialized. A local value equal to either that baseline or the current
223
+ * rail is not new work. Anything else is a local contribution.
224
+ */
225
+ function hasLocalContribution(card, plan, baseline, checkpoint = null) {
226
+ if (!plan?.card) return true;
227
+ const onRail = samePosition(card, plan.card);
228
+ const atBaseline = baseline
229
+ && card.head?.branch === baseline.branch
230
+ && card.head?.oid === baseline.baseOid;
231
+ if (!onRail && !atBaseline) return true;
232
+
233
+ const currentRefs = oidMap(card.refs);
234
+ const railRefs = oidMap(plan.branches);
235
+ const baselineRefs = oidMap(baseline?.branches);
236
+ for (const name of new Set([...currentRefs.keys(), ...baselineRefs.keys()])) {
237
+ const current = currentRefs.get(name) ?? null;
238
+ const shared = railRefs.get(name) ?? null;
239
+ const previous = baselineRefs.get(name) ?? null;
240
+ if (current !== shared && (!baselineRefs.has(name) || current !== previous)) return true;
241
+ }
242
+
243
+ if (
244
+ checkpoint
245
+ && checkpoint.baseOid === card.head?.oid
246
+ && checkpoint.branch === card.head?.branch
247
+ ) {
248
+ const latest = [...(plan.transitions || [])]
249
+ .reverse()
250
+ .find((entry) => entry.machine === checkpoint.machine);
251
+ if (checkpointIdentity(latest) !== checkpointIdentity(checkpoint)) return true;
252
+ }
253
+ return false;
254
+ }
255
+
256
+ function hasObjectUpgrade(card, plan) {
257
+ if (!plan?.card || !samePosition(card, plan.card)) return false;
258
+ if (card.objects?.kind === 'bundle' && plan.card.objects?.kind !== 'bundle') return true;
259
+ const shared = new Map((plan.branches || []).map((ref) => [ref.name, ref]));
260
+ return (card.refs || []).some((ref) => {
261
+ const onRail = shared.get(ref.name);
262
+ return onRail
263
+ && onRail.oid === ref.oid
264
+ && ref.objects?.kind === 'bundle'
265
+ && onRail.objects?.kind !== 'bundle';
266
+ });
267
+ }
268
+
224
269
  function validVersion(value) {
225
270
  return Number.isInteger(value) && value > 0;
226
271
  }
@@ -236,19 +281,42 @@ function officialVersion(resourceId, mutationId) {
236
281
  return Number.isInteger(mutation?.officialCloudVersion) ? mutation.officialCloudVersion : null;
237
282
  }
238
283
 
239
- function stampBranches(refs, previousBranches, mutationId, resourceId) {
284
+ function stampBranches(
285
+ refs,
286
+ previousBranches,
287
+ sharedBranches,
288
+ baselineBranches,
289
+ mutationId,
290
+ resourceId,
291
+ ) {
240
292
  const current = new Map((refs || []).map((ref) => [ref.name, ref]));
241
293
  const previous = new Map((previousBranches || []).map((ref) => [ref.name, ref]));
294
+ const shared = new Map((sharedBranches || []).map((ref) => [ref.name, ref]));
295
+ const baseline = new Map((baselineBranches || []).map((ref) => [ref.name, ref]));
242
296
  const names = [...new Set([...current.keys(), ...previous.keys()])].sort();
243
297
  return names.map((name) => {
244
298
  const observed = current.get(name);
245
299
  const before = previous.get(name);
300
+ const onRail = shared.get(name);
301
+ const materialized = baseline.get(name);
246
302
  const oid = observed?.oid ?? null;
247
303
  const unchanged = before && (before.oid ?? null) === oid;
248
- const reusable = unchanged
304
+ const previousReusable = unchanged
249
305
  && before.mutationId
250
306
  && (validVersion(before.version) || knownMutation(resourceId, before.mutationId));
251
- const mutation = reusable ? before.mutationId : mutationId;
307
+ const railReusable = onRail
308
+ && (onRail.oid ?? null) === oid
309
+ && onRail.mutationId
310
+ && validVersion(onRail.version);
311
+ const baselineReusable = materialized
312
+ && (materialized.oid ?? null) === oid
313
+ && materialized.mutationId
314
+ && validVersion(materialized.version);
315
+ const mutation = previousReusable
316
+ ? before.mutationId
317
+ : railReusable
318
+ ? onRail.mutationId
319
+ : baselineReusable ? materialized.mutationId : mutationId;
252
320
  return {
253
321
  name,
254
322
  oid,
@@ -279,7 +347,7 @@ function isCommittedCard(card) {
279
347
  return typeof card?.head?.oid === 'string' && /^[0-9a-f]{40,64}$/.test(card.head.oid);
280
348
  }
281
349
 
282
- function stampCards(cards, previousCards, mutationId, project) {
350
+ function stampCards(cards, previousCards, sharedPlan, baseline, mutationId, project) {
283
351
  const previousByReplica = new Map(
284
352
  (previousCards || []).filter((card) => card?.replica).map((card) => [card.replica, card]),
285
353
  );
@@ -290,8 +358,20 @@ function stampCards(cards, previousCards, mutationId, project) {
290
358
  && (
291
359
  validVersion(previous.positionVersion)
292
360
  || knownMutation(project.resourceId, previous.positionMutationId)
293
- );
294
- const positionMutationId = reusablePosition ? previous.positionMutationId : mutationId;
361
+ );
362
+ const reusableRailPosition = samePosition(sharedPlan?.card, card)
363
+ && sharedPlan.card.positionMutationId
364
+ && validVersion(sharedPlan.card.positionVersion);
365
+ const reusableBaselinePosition = baseline
366
+ && card.head?.branch === baseline.branch
367
+ && card.head?.oid === baseline.baseOid
368
+ && baseline.cardMutationId
369
+ && validVersion(baseline.cardVersion);
370
+ const positionMutationId = reusablePosition
371
+ ? previous.positionMutationId
372
+ : reusableRailPosition
373
+ ? sharedPlan.card.positionMutationId
374
+ : reusableBaselinePosition ? baseline.cardMutationId : mutationId;
295
375
  const checkpoint = readCheckpoint(machineId(), project.repoKey);
296
376
  const currentCheckpoint = checkpoint
297
377
  && checkpoint.baseOid === card.head?.oid
@@ -314,7 +394,9 @@ function stampCards(cards, previousCards, mutationId, project) {
314
394
  positionMutationId,
315
395
  branches: stampBranches(
316
396
  card.refs,
317
- sameBase ? previous?.branches : [],
397
+ previous?.branches,
398
+ sharedPlan?.branches,
399
+ baseline?.branches,
318
400
  mutationId,
319
401
  project.resourceId,
320
402
  ),
@@ -362,8 +444,30 @@ function publishProject(project, rawCards) {
362
444
  }
363
445
  }
364
446
 
447
+ const reducer = require('./reducer');
448
+ const sharedPlan = reducer.reduceWall(readProjectWall(project)).get(project.repoKey);
449
+ const baseline = reducer.readRepoState(project.repoKey);
450
+ const previousReplicas = new Set((previousEntry?.cards || []).map((card) => card.replica));
451
+ const currentReplicas = new Set(rawCards.map((card) => card.replica));
452
+ const removed = [...previousReplicas].some((replica) => !currentReplicas.has(replica));
453
+ const contributes = rawCards.some((card) => {
454
+ const checkpoint = readCheckpoint(machineId(), project.repoKey);
455
+ return hasLocalContribution(card, sharedPlan, baseline, checkpoint)
456
+ || hasObjectUpgrade(card, sharedPlan);
457
+ });
458
+ if (!removed && !contributes) {
459
+ service.counters.publishSkips += 1;
460
+ return { published: false, reason: 'shared-state-covered', repoKey: project.repoKey };
461
+ }
365
462
  const mutationId = `mut_project_${crypto.randomBytes(16).toString('base64url')}`;
366
- const cards = stampCards(rawCards, previousEntry?.cards, mutationId, project);
463
+ const cards = stampCards(
464
+ rawCards,
465
+ previousEntry?.cards,
466
+ sharedPlan,
467
+ baseline,
468
+ mutationId,
469
+ project,
470
+ );
367
471
  const mine = JSON.stringify({
368
472
  wall: WALL_CONTRACT,
369
473
  project: project.repoKey,
@@ -566,6 +670,7 @@ function wallStats() {
566
670
  module.exports = {
567
671
  WALL_CONTRACT,
568
672
  bindProject,
673
+ hasLocalContribution,
569
674
  isCommittedCard,
570
675
  projectFile,
571
676
  projectForRepo,
@@ -573,6 +678,7 @@ module.exports = {
573
678
  publishedCardFor,
574
679
  publishWall,
575
680
  readWall,
681
+ stampCards,
576
682
  startWallService,
577
683
  stopWallService,
578
684
  wallStats,