amalgm 0.1.189 → 0.1.190

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.190",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -31,6 +31,7 @@ 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,
@@ -71,6 +72,60 @@ test('no commit means no shared card', () => {
71
72
  assert.equal(isCommittedCard({ head: { branch: 'main', oid: 'a'.repeat(40) } }), true);
72
73
  });
73
74
 
75
+ test('only a value different from both the materialized baseline and the rail is new work', () => {
76
+ const oidA = 'a'.repeat(40);
77
+ const oidB = 'b'.repeat(40);
78
+ const oidC = 'c'.repeat(40);
79
+ const local = {
80
+ head: { branch: 'main', oid: oidA, detached: false },
81
+ refs: [{ name: 'main', oid: oidA }],
82
+ };
83
+ const plan = {
84
+ card: { head: { branch: 'main', oid: oidA, detached: false } },
85
+ branches: [
86
+ { name: 'main', oid: oidA },
87
+ { name: 'feature', oid: oidB },
88
+ ],
89
+ transitions: [],
90
+ };
91
+ const baseline = {
92
+ branch: 'main',
93
+ baseOid: oidA,
94
+ branches: [{ name: 'main', oid: oidA }],
95
+ };
96
+
97
+ assert.equal(hasLocalContribution(local, plan, baseline), false);
98
+ assert.equal(hasLocalContribution({
99
+ ...local,
100
+ refs: [...local.refs, { name: 'feature', oid: oidB }],
101
+ }, plan, baseline), false);
102
+ assert.equal(hasLocalContribution({
103
+ ...local,
104
+ head: { branch: 'main', oid: oidC, detached: false },
105
+ refs: [{ name: 'main', oid: oidC }],
106
+ }, plan, baseline), true);
107
+ assert.equal(hasLocalContribution(local, plan, {
108
+ ...baseline,
109
+ branches: [...baseline.branches, { name: 'feature', oid: oidB }],
110
+ }), true);
111
+
112
+ const remoteAdvance = {
113
+ ...plan,
114
+ card: { head: { branch: 'main', oid: oidC, detached: false } },
115
+ branches: [{ name: 'main', oid: oidC }],
116
+ };
117
+ assert.equal(hasLocalContribution(local, remoteAdvance, baseline), false);
118
+ assert.equal(hasLocalContribution(local, plan, baseline, {
119
+ checkpoint: 'repo-checkpoint@7',
120
+ machine: 'machine-a',
121
+ branch: 'main',
122
+ baseOid: oidA,
123
+ from: { revision: null, indexDiff: '', worktreeDiff: '' },
124
+ to: { revision: 'local-work', indexDiff: '', worktreeDiff: 'patch' },
125
+ parked: [],
126
+ }), true);
127
+ });
128
+
74
129
  test('a collaborator binds the same repo to the owner project, not a parallel project', () => {
75
130
  const shared = projectResourceId('repo-shared', '22222222-2222-4222-8222-222222222222');
76
131
  const localPath = path.join(tempRoot, 'projects/shared');
@@ -129,7 +184,7 @@ test('a repo project carries exactly one line per machine, replaced in place', a
129
184
  });
130
185
  const sealed = publishWall(repoKey);
131
186
  assert.equal(sealed.published, false);
132
- assert.equal(sealed.reason, 'unchanged');
187
+ assert.equal(sealed.reason, 'shared-state-covered');
133
188
  const sealedCard = readWall(repoKey)[0].cards[0];
134
189
  assert.equal(sealedCard.positionMutationId, first.mutationId);
135
190
  assert.equal(sealedCard.positionVersion, 1);
@@ -137,7 +192,7 @@ test('a repo project carries exactly one line per machine, replaced in place', a
137
192
 
138
193
  const again = publishWall(repoKey);
139
194
  assert.equal(again.published, false);
140
- assert.equal(again.reason, 'unchanged');
195
+ assert.equal(again.reason, 'shared-state-covered');
141
196
 
142
197
  fs.writeFileSync(path.join(repo, 'new.txt'), 'x\n');
143
198
  await refreshRepoCards();
@@ -192,6 +247,34 @@ test('checkpoint transitions append until the next card and carry their official
192
247
  assert.deepEqual(history.map((entry) => entry.mutationId), [first.mutationId, second.mutationId]);
193
248
  });
194
249
 
250
+ test('side branches keep one rail operation while the checked-out card advances', async () => {
251
+ const repo = path.join(tempRoot, 'projects/app');
252
+ const before = readWall(repoKey)[0].cards[0];
253
+ const branch = 'storm/persistent-ref';
254
+
255
+ await run(repo, 'branch', branch);
256
+ await refreshRepoCards();
257
+ const created = publishWall(repoKey);
258
+ assert.equal(created.published, true);
259
+ let card = readWall(repoKey)[0].cards[0];
260
+ const ref = card.branches.find((entry) => entry.name === branch);
261
+ assert.equal(card.positionMutationId, before.positionMutationId);
262
+ assert.equal(ref.mutationId, created.mutationId);
263
+
264
+ acknowledgeCloudMutation(project.resourceId, created.mutationId, {
265
+ version: 3,
266
+ committedAt: '2026-07-23T00:00:04.000Z',
267
+ });
268
+ await run(repo, 'add', 'new.txt');
269
+ await run(repo, 'commit', '-m', 'advance without moving side branch');
270
+ await refreshRepoCards();
271
+ const advanced = publishWall(repoKey);
272
+ assert.equal(advanced.published, true);
273
+ card = readWall(repoKey)[0].cards[0];
274
+ assert.equal(card.positionMutationId, advanced.mutationId);
275
+ assert.equal(card.branches.find((entry) => entry.name === branch).mutationId, created.mutationId);
276
+ });
277
+
195
278
  test('a peer machine line is preserved byte-for-byte through local publishes', async () => {
196
279
  const peerLine = JSON.stringify({
197
280
  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,27 @@ 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(refs, previousBranches, sharedBranches, mutationId, resourceId) {
240
285
  const current = new Map((refs || []).map((ref) => [ref.name, ref]));
241
286
  const previous = new Map((previousBranches || []).map((ref) => [ref.name, ref]));
287
+ const shared = new Map((sharedBranches || []).map((ref) => [ref.name, ref]));
242
288
  const names = [...new Set([...current.keys(), ...previous.keys()])].sort();
243
289
  return names.map((name) => {
244
290
  const observed = current.get(name);
245
291
  const before = previous.get(name);
292
+ const onRail = shared.get(name);
246
293
  const oid = observed?.oid ?? null;
247
294
  const unchanged = before && (before.oid ?? null) === oid;
248
- const reusable = unchanged
295
+ const previousReusable = unchanged
249
296
  && before.mutationId
250
297
  && (validVersion(before.version) || knownMutation(resourceId, before.mutationId));
251
- const mutation = reusable ? before.mutationId : mutationId;
298
+ const railReusable = onRail
299
+ && (onRail.oid ?? null) === oid
300
+ && onRail.mutationId
301
+ && validVersion(onRail.version);
302
+ const mutation = previousReusable
303
+ ? before.mutationId
304
+ : railReusable ? onRail.mutationId : mutationId;
252
305
  return {
253
306
  name,
254
307
  oid,
@@ -279,7 +332,7 @@ function isCommittedCard(card) {
279
332
  return typeof card?.head?.oid === 'string' && /^[0-9a-f]{40,64}$/.test(card.head.oid);
280
333
  }
281
334
 
282
- function stampCards(cards, previousCards, mutationId, project) {
335
+ function stampCards(cards, previousCards, sharedPlan, mutationId, project) {
283
336
  const previousByReplica = new Map(
284
337
  (previousCards || []).filter((card) => card?.replica).map((card) => [card.replica, card]),
285
338
  );
@@ -290,8 +343,13 @@ function stampCards(cards, previousCards, mutationId, project) {
290
343
  && (
291
344
  validVersion(previous.positionVersion)
292
345
  || knownMutation(project.resourceId, previous.positionMutationId)
293
- );
294
- const positionMutationId = reusablePosition ? previous.positionMutationId : mutationId;
346
+ );
347
+ const reusableRailPosition = samePosition(sharedPlan?.card, card)
348
+ && sharedPlan.card.positionMutationId
349
+ && validVersion(sharedPlan.card.positionVersion);
350
+ const positionMutationId = reusablePosition
351
+ ? previous.positionMutationId
352
+ : reusableRailPosition ? sharedPlan.card.positionMutationId : mutationId;
295
353
  const checkpoint = readCheckpoint(machineId(), project.repoKey);
296
354
  const currentCheckpoint = checkpoint
297
355
  && checkpoint.baseOid === card.head?.oid
@@ -314,7 +372,8 @@ function stampCards(cards, previousCards, mutationId, project) {
314
372
  positionMutationId,
315
373
  branches: stampBranches(
316
374
  card.refs,
317
- sameBase ? previous?.branches : [],
375
+ previous?.branches,
376
+ sharedPlan?.branches,
318
377
  mutationId,
319
378
  project.resourceId,
320
379
  ),
@@ -362,8 +421,23 @@ function publishProject(project, rawCards) {
362
421
  }
363
422
  }
364
423
 
424
+ const reducer = require('./reducer');
425
+ const sharedPlan = reducer.reduceWall(readProjectWall(project)).get(project.repoKey);
426
+ const baseline = reducer.readRepoState(project.repoKey);
427
+ const previousReplicas = new Set((previousEntry?.cards || []).map((card) => card.replica));
428
+ const currentReplicas = new Set(rawCards.map((card) => card.replica));
429
+ const removed = [...previousReplicas].some((replica) => !currentReplicas.has(replica));
430
+ const contributes = rawCards.some((card) => {
431
+ const checkpoint = readCheckpoint(machineId(), project.repoKey);
432
+ return hasLocalContribution(card, sharedPlan, baseline, checkpoint)
433
+ || hasObjectUpgrade(card, sharedPlan);
434
+ });
435
+ if (!removed && !contributes) {
436
+ service.counters.publishSkips += 1;
437
+ return { published: false, reason: 'shared-state-covered', repoKey: project.repoKey };
438
+ }
365
439
  const mutationId = `mut_project_${crypto.randomBytes(16).toString('base64url')}`;
366
- const cards = stampCards(rawCards, previousEntry?.cards, mutationId, project);
440
+ const cards = stampCards(rawCards, previousEntry?.cards, sharedPlan, mutationId, project);
367
441
  const mine = JSON.stringify({
368
442
  wall: WALL_CONTRACT,
369
443
  project: project.repoKey,
@@ -566,6 +640,7 @@ function wallStats() {
566
640
  module.exports = {
567
641
  WALL_CONTRACT,
568
642
  bindProject,
643
+ hasLocalContribution,
569
644
  isCommittedCard,
570
645
  projectFile,
571
646
  projectForRepo,