amalgm 0.1.208 → 0.1.210

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.
@@ -17,7 +17,7 @@ const CLAIM_LIMIT = 16;
17
17
  const CLAIM_LEASE_MS = 30_000;
18
18
  const RETRY_MS = 1_000;
19
19
  const REAUTHORIZE_MS = 30_000;
20
- const REPO_OBJECT_REQUEST_TIMEOUT_MS = 2_000;
20
+ const REPO_OBJECT_REQUEST_TIMEOUT_MS = 15_000;
21
21
 
22
22
  function createSharedRealtimeTunnel({
23
23
  send,
@@ -475,35 +475,45 @@ function createSharedRealtimeTunnel({
475
475
  }
476
476
 
477
477
  function handleRepoObjectStored(frame) {
478
- const object = repoUploadInflight.get(frame.request_id);
478
+ const object = repoUploadInflight.get(frame.request_id)
479
+ || [...repoUploadInflight.values()].find((entry) => entry.sha256 === frame.sha256);
479
480
  if (!object || object.sha256 !== frame.sha256) return;
480
481
  void localStateJson('POST', '/state/repo-objects/uploads/ack', {
481
482
  resourceId: object.resourceId,
482
483
  sha256: object.sha256,
483
484
  })
484
485
  .then(() => {
485
- repoUploadInflight.delete(frame.request_id);
486
+ for (const [requestId, entry] of repoUploadInflight) {
487
+ if (entry.sha256 === object.sha256) repoUploadInflight.delete(requestId);
488
+ }
486
489
  scheduleRepoObjects();
487
490
  })
488
491
  .catch(() => {
489
- repoUploadInflight.delete(frame.request_id);
492
+ for (const [requestId, entry] of repoUploadInflight) {
493
+ if (entry.sha256 === object.sha256) repoUploadInflight.delete(requestId);
494
+ }
490
495
  scheduleRepoObjects(RETRY_MS);
491
496
  });
492
497
  }
493
498
 
494
499
  function handleRepoObjectData(frame) {
495
- const object = repoDownloadInflight.get(frame.request_id);
500
+ const object = repoDownloadInflight.get(frame.request_id)
501
+ || [...repoDownloadInflight.values()].find((entry) => entry.sha256 === frame.sha256);
496
502
  if (!object || object.sha256 !== frame.sha256) return;
497
503
  void localStateJson('POST', '/state/repo-objects/downloads/receive', {
498
504
  resourceId: object.resourceId,
499
505
  sha256: object.sha256,
500
506
  dataBase64: frame.data_b64,
501
507
  }).then(() => {
502
- repoDownloadInflight.delete(frame.request_id);
508
+ for (const [requestId, entry] of repoDownloadInflight) {
509
+ if (entry.sha256 === object.sha256) repoDownloadInflight.delete(requestId);
510
+ }
503
511
  scheduleRepoObjects();
504
512
  }).catch((error) => {
505
513
  warn(`repo object ${object.sha256} was not stored locally: ${error?.message || error}`);
506
- repoDownloadInflight.delete(frame.request_id);
514
+ for (const [requestId, entry] of repoDownloadInflight) {
515
+ if (entry.sha256 === object.sha256) repoDownloadInflight.delete(requestId);
516
+ }
507
517
  scheduleRepoObjects(RETRY_MS);
508
518
  });
509
519
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.208",
3
+ "version": "0.1.210",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -127,6 +127,7 @@ test('repo object chunks retry when an acknowledgement is lost', async () => {
127
127
  const uploadBytes = Buffer.from('retry this content-addressed chunk');
128
128
  const uploadSha = crypto.createHash('sha256').update(uploadBytes).digest('hex');
129
129
  const resourceId = 'shr_cp1abcdefghijklmnopqrstuvwx';
130
+ let acknowledgements = 0;
130
131
  const server = http.createServer((req, res) => {
131
132
  req.resume();
132
133
  req.on('end', () => {
@@ -147,6 +148,9 @@ test('repo object chunks retry when an acknowledgement is lost', async () => {
147
148
  }));
148
149
  } else if (req.url === '/state/repo-objects/downloads/claim') {
149
150
  res.end('{"download":null}');
151
+ } else if (req.url === '/state/repo-objects/uploads/ack') {
152
+ acknowledgements += 1;
153
+ res.end('{"changed":true,"completed":true}');
150
154
  } else {
151
155
  res.end('{}');
152
156
  }
@@ -173,6 +177,13 @@ test('repo object chunks retry when an acknowledgement is lost', async () => {
173
177
  const puts = frames.filter((frame) => frame.type === 'repo.object.put');
174
178
  assert.notEqual(puts[0].request_id, puts[1].request_id);
175
179
  assert.equal(puts[0].sha256, puts[1].sha256);
180
+ tunnel.handleFrame({
181
+ type: 'repo.object.stored',
182
+ request_id: puts[0].request_id,
183
+ sha256: uploadSha,
184
+ bytes: uploadBytes.length,
185
+ });
186
+ await until(() => acknowledgements === 1, 'the late upload acknowledgement');
176
187
  } finally {
177
188
  tunnel.stop();
178
189
  server.close();
@@ -137,6 +137,34 @@ test('only a value different from both the materialized baseline and the rail is
137
137
  branches: [{ name: 'main', oid: oidA }],
138
138
  },
139
139
  ), false);
140
+ assert.equal(hasLocalContribution(
141
+ {
142
+ head: { branch: 'main', oid: oidB, detached: false },
143
+ refs: [{ name: 'main', oid: oidB }],
144
+ },
145
+ remoteAdvance,
146
+ baseline,
147
+ null,
148
+ {
149
+ positionVersion: 10,
150
+ head: { branch: 'main', oid: oidB, detached: false },
151
+ branches: [{ name: 'main', oid: oidB, version: 10 }],
152
+ },
153
+ ), false);
154
+ assert.equal(hasLocalContribution(
155
+ {
156
+ head: { branch: 'main', oid: oidB, detached: false },
157
+ refs: [{ name: 'main', oid: oidB }],
158
+ },
159
+ remoteAdvance,
160
+ baseline,
161
+ null,
162
+ {
163
+ positionVersion: null,
164
+ head: { branch: 'main', oid: oidB, detached: false },
165
+ branches: [{ name: 'main', oid: oidB, version: null }],
166
+ },
167
+ ), true);
140
168
  assert.equal(hasLocalContribution(
141
169
  local,
142
170
  plan,
@@ -148,8 +176,8 @@ test('only a value different from both the materialized baseline and the rail is
148
176
  {
149
177
  head: { branch: 'main', oid: oidA, detached: false },
150
178
  branches: [
151
- { name: 'main', oid: oidA },
152
- { name: 'feature', oid: oidB },
179
+ { name: 'main', oid: oidA, version: 10 },
180
+ { name: 'feature', oid: oidB, version: 10 },
153
181
  ],
154
182
  },
155
183
  ), true);
@@ -272,6 +300,13 @@ test('a repo project appends immutable machine records', async () => {
272
300
  assert.equal(entries[0].cards[0].positionMutationId, first.mutationId);
273
301
  assert.equal(entries[0].cards[0].positionVersion, null);
274
302
  assert.equal(publishedCardFor(refreshed.cards[0]), null);
303
+ const awaitingNumber = publishWall(repoKey);
304
+ assert.equal(awaitingNumber.published, false);
305
+ assert.equal(awaitingNumber.reason, 'unchanged');
306
+ assert.equal(
307
+ require('../workspace/reducer').localCardMayMaterialize(null, awaitingNumber),
308
+ false,
309
+ );
275
310
 
276
311
  acknowledgeCloudMutation(project.resourceId, first.mutationId, {
277
312
  version: 1,
@@ -144,6 +144,27 @@ function parseWallLine(line) {
144
144
  }
145
145
  }
146
146
 
147
+ function sealCardVersions(project, card) {
148
+ return {
149
+ ...card,
150
+ positionVersion: validVersion(card.positionVersion)
151
+ ? card.positionVersion
152
+ : officialVersion(project.resourceId, card.positionMutationId),
153
+ branches: (card.branches || []).map((ref) => ({
154
+ ...ref,
155
+ version: validVersion(ref.version)
156
+ ? ref.version
157
+ : officialVersion(project.resourceId, ref.mutationId),
158
+ })),
159
+ checkpoints: (card.checkpoints || []).map((checkpoint) => ({
160
+ ...checkpoint,
161
+ version: validVersion(checkpoint.version)
162
+ ? checkpoint.version
163
+ : officialVersion(project.resourceId, checkpoint.mutationId),
164
+ })),
165
+ };
166
+ }
167
+
147
168
  function readProjectWall(project) {
148
169
  if (!fs.existsSync(project.file)) return [];
149
170
  const entries = new Map();
@@ -154,24 +175,7 @@ function readProjectWall(project) {
154
175
  entries.set(entry.machine, {
155
176
  ...entry,
156
177
  resourceId: project.resourceId,
157
- cards: entry.cards.map((card) => ({
158
- ...card,
159
- positionVersion: validVersion(card.positionVersion)
160
- ? card.positionVersion
161
- : officialVersion(project.resourceId, card.positionMutationId),
162
- branches: (card.branches || []).map((ref) => ({
163
- ...ref,
164
- version: validVersion(ref.version)
165
- ? ref.version
166
- : officialVersion(project.resourceId, ref.mutationId),
167
- })),
168
- checkpoints: (card.checkpoints || []).map((checkpoint) => ({
169
- ...checkpoint,
170
- version: validVersion(checkpoint.version)
171
- ? checkpoint.version
172
- : officialVersion(project.resourceId, checkpoint.mutationId),
173
- })),
174
- })),
178
+ cards: entry.cards.map((card) => sealCardVersions(project, card)),
175
179
  });
176
180
  }
177
181
  return [...entries.values()];
@@ -231,12 +235,20 @@ function hasLocalContribution(card, plan, baseline, checkpoint = null, previousC
231
235
  const atBaseline = baseline
232
236
  && card.head?.branch === baseline.branch
233
237
  && card.head?.oid === baseline.baseOid;
234
- if (!onRail && !atBaseline) return true;
238
+ const alreadyPublished = validVersion(previousCard?.positionVersion)
239
+ && samePosition(card, previousCard);
240
+ const reversesPublishedSharedPosition = previousCard
241
+ && validVersion(previousCard.positionVersion)
242
+ && samePosition(previousCard, plan.card)
243
+ && !alreadyPublished;
244
+ if ((!onRail && !atBaseline && !alreadyPublished) || reversesPublishedSharedPosition) return true;
235
245
 
236
246
  const currentRefs = oidMap(card.refs);
237
247
  const railRefs = oidMap(plan.branches);
238
248
  const baselineRefs = oidMap(baseline?.branches);
239
- const previousRefs = oidMap(previousCard?.branches || previousCard?.refs);
249
+ const previousRefs = new Map(
250
+ (previousCard?.branches || previousCard?.refs || []).map((ref) => [ref.name, ref]),
251
+ );
240
252
  for (const name of new Set([
241
253
  ...currentRefs.keys(),
242
254
  ...baselineRefs.keys(),
@@ -245,13 +257,16 @@ function hasLocalContribution(card, plan, baseline, checkpoint = null, previousC
245
257
  const current = currentRefs.get(name) ?? null;
246
258
  const shared = railRefs.get(name) ?? null;
247
259
  const previous = baselineRefs.get(name) ?? null;
248
- const published = previousRefs.get(name) ?? null;
249
- const reversesPublishedSharedValue = previousRefs.has(name)
260
+ const publishedRef = previousRefs.get(name);
261
+ const published = publishedRef?.oid ?? null;
262
+ const matchesBaseline = baselineRefs.has(name) && current === previous;
263
+ const matchesPublished = validVersion(publishedRef?.version) && current === published;
264
+ const reversesPublishedSharedValue = validVersion(publishedRef?.version)
250
265
  && published === shared
251
266
  && current !== published;
252
267
  if (
253
268
  current !== shared
254
- && (!baselineRefs.has(name) || current !== previous || reversesPublishedSharedValue)
269
+ && ((!matchesBaseline && !matchesPublished) || reversesPublishedSharedValue)
255
270
  ) return true;
256
271
  }
257
272
 
@@ -513,12 +528,13 @@ function publishProject(project, rawCards) {
513
528
  const reducer = require('./reducer');
514
529
  const sharedPlan = reducer.reduceWall(readProjectWall(project)).get(project.repoKey);
515
530
  const baseline = reducer.readRepoState(project.repoKey);
516
- const previousReplicas = new Set((previousEntry?.cards || []).map((card) => card.replica));
531
+ const previousCards = (previousEntry?.cards || []).map((card) => sealCardVersions(project, card));
532
+ const previousReplicas = new Set(previousCards.map((card) => card.replica));
517
533
  const currentReplicas = new Set(rawCards.map((card) => card.replica));
518
534
  const removed = [...previousReplicas].some((replica) => !currentReplicas.has(replica));
519
535
  const contributes = rawCards.some((card) => {
520
536
  const checkpoint = readCheckpoint(machineId(), project.repoKey);
521
- const previousCard = previousEntry?.cards?.find((entry) => entry.replica === card.replica);
537
+ const previousCard = previousCards.find((entry) => entry.replica === card.replica);
522
538
  return hasLocalContribution(card, sharedPlan, baseline, checkpoint, previousCard)
523
539
  || hasObjectUpgrade(card, sharedPlan);
524
540
  });
@@ -529,7 +545,7 @@ function publishProject(project, rawCards) {
529
545
  const mutationId = `mut_project_${crypto.randomBytes(16).toString('base64url')}`;
530
546
  const cards = stampCards(
531
547
  rawCards,
532
- previousEntry?.cards,
548
+ previousCards,
533
549
  sharedPlan,
534
550
  baseline,
535
551
  mutationId,