amalgm 0.1.245 → 0.1.246
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/README.md +12 -0
- package/lib/cli.js +146 -8
- package/lib/layout.js +89 -4
- package/lib/shared-realtime-tunnel.js +384 -43
- package/lib/supervisor.js +0 -3
- package/package.json +2 -2
- package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
- package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
- package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
- package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
- package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
- package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
- package/runtime/scripts/amalgm-mcp/index.js +4 -0
- package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
- package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
- package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
- package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
- package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
- package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
- package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
- package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
- package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
- package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
- package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
- package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
- package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
- package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
- package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
- package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
- package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
- package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
- package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
- package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
- package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
- package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
- package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
- package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
- package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
- package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
- package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
- package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
- package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
- package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
- package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
- package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
|
@@ -6,7 +6,6 @@ const fs = require('fs');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const { execFile } = require('child_process');
|
|
9
|
-
const { EventEmitter } = require('events');
|
|
10
9
|
const { promisify } = require('util');
|
|
11
10
|
|
|
12
11
|
const execFileAsync = promisify(execFile);
|
|
@@ -24,7 +23,6 @@ const {
|
|
|
24
23
|
repoCardServiceStats,
|
|
25
24
|
startRepoCardService,
|
|
26
25
|
stopRepoCardService,
|
|
27
|
-
_private,
|
|
28
26
|
} = require('../workspace/cards');
|
|
29
27
|
|
|
30
28
|
test.after(() => {
|
|
@@ -318,7 +316,7 @@ test('coverage shift and deletion inside a single pass still reads as missing',
|
|
|
318
316
|
assert.equal(findCard(settled.cards, repo), undefined);
|
|
319
317
|
});
|
|
320
318
|
|
|
321
|
-
test('the live service hears a commit through the git-dir doorbell', async (
|
|
319
|
+
test('the live service hears a commit through the git-dir doorbell', async () => {
|
|
322
320
|
const repo = await makeRepo('projects/belled');
|
|
323
321
|
const projects = path.join(tempRoot, 'projects');
|
|
324
322
|
upsertWorkspace({ path: projects, name: 'Projects', source: 'computer' });
|
|
@@ -326,13 +324,6 @@ test('the live service hears a commit through the git-dir doorbell', async (t) =
|
|
|
326
324
|
await refreshRepoCards();
|
|
327
325
|
const before = findCard(listRepoCards(), repo);
|
|
328
326
|
assert.equal(before.health, 'healthy');
|
|
329
|
-
// fs.watch reports resource exhaustion asynchronously on macOS.
|
|
330
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
331
|
-
if (!_private.watcherComplete(before.replica)) {
|
|
332
|
-
stopRepoCardService();
|
|
333
|
-
t.skip('the host refused part or all of the required fs.watch set');
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
327
|
|
|
337
328
|
fs.writeFileSync(path.join(repo, 'ding.txt'), 'ding\n');
|
|
338
329
|
await run(repo, 'add', '.');
|
|
@@ -350,61 +341,7 @@ test('the live service hears a commit through the git-dir doorbell', async (t) =
|
|
|
350
341
|
assert.ok(repoCardServiceStats().doorbellRings >= 1);
|
|
351
342
|
});
|
|
352
343
|
|
|
353
|
-
test('
|
|
354
|
-
const repo = await makeRepo('projects/bounded-doorbell');
|
|
355
|
-
const projects = path.join(tempRoot, 'projects');
|
|
356
|
-
upsertWorkspace({ path: projects, name: 'Projects', source: 'computer' });
|
|
357
|
-
stopRepoCardService();
|
|
358
|
-
await refreshRepoCards();
|
|
359
|
-
|
|
360
|
-
const originalWatch = fs.watch;
|
|
361
|
-
let openHandles = 0;
|
|
362
|
-
let maximumOpenHandles = 0;
|
|
363
|
-
let handleLimit = Number.POSITIVE_INFINITY;
|
|
364
|
-
fs.watch = () => {
|
|
365
|
-
if (openHandles >= handleLimit) {
|
|
366
|
-
const error = new Error('simulated watch-handle ceiling');
|
|
367
|
-
error.code = 'EMFILE';
|
|
368
|
-
throw error;
|
|
369
|
-
}
|
|
370
|
-
openHandles += 1;
|
|
371
|
-
maximumOpenHandles = Math.max(maximumOpenHandles, openHandles);
|
|
372
|
-
const watcher = new EventEmitter();
|
|
373
|
-
let closed = false;
|
|
374
|
-
watcher.close = () => {
|
|
375
|
-
if (closed) return;
|
|
376
|
-
closed = true;
|
|
377
|
-
openHandles -= 1;
|
|
378
|
-
};
|
|
379
|
-
watcher.unref = () => watcher;
|
|
380
|
-
return watcher;
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
try {
|
|
384
|
-
startRepoCardService();
|
|
385
|
-
await refreshRepoCards();
|
|
386
|
-
const card = findCard(listRepoCards(), repo);
|
|
387
|
-
const initialHandles = _private.watcherCount(card.replica);
|
|
388
|
-
const initialTotal = openHandles;
|
|
389
|
-
assert.ok(initialHandles > 0);
|
|
390
|
-
assert.equal(_private.watcherComplete(card.replica), true);
|
|
391
|
-
assert.ok(initialTotal >= initialHandles);
|
|
392
|
-
|
|
393
|
-
handleLimit = initialTotal;
|
|
394
|
-
maximumOpenHandles = openHandles;
|
|
395
|
-
_private.rebuildDoorbell(card.replica, card);
|
|
396
|
-
|
|
397
|
-
assert.equal(_private.watcherComplete(card.replica), true);
|
|
398
|
-
assert.equal(openHandles, initialTotal);
|
|
399
|
-
assert.equal(maximumOpenHandles, initialTotal);
|
|
400
|
-
assert.equal(repoCardServiceStats().incompleteDoorbells, 0);
|
|
401
|
-
} finally {
|
|
402
|
-
stopRepoCardService();
|
|
403
|
-
fs.watch = originalWatch;
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
test('the targeted doorbell path hears nested branch creation and deletion', async () => {
|
|
344
|
+
test('the live service hears nested branch creation and deletion', async () => {
|
|
408
345
|
const repo = await makeRepo('projects/belled-refs');
|
|
409
346
|
const projects = path.join(tempRoot, 'projects');
|
|
410
347
|
upsertWorkspace({ path: projects, name: 'Projects', source: 'computer' });
|
|
@@ -414,7 +351,6 @@ test('the targeted doorbell path hears nested branch creation and deletion', asy
|
|
|
414
351
|
const before = findCard(listRepoCards(), repo);
|
|
415
352
|
|
|
416
353
|
await run(repo, 'branch', branch);
|
|
417
|
-
_private.ringDoorbell(before.replica);
|
|
418
354
|
let deadline = Date.now() + 5_000;
|
|
419
355
|
let created = findCard(listRepoCards(), repo);
|
|
420
356
|
while (!created.refs.some((ref) => ref.name === branch) && Date.now() < deadline) {
|
|
@@ -424,7 +360,6 @@ test('the targeted doorbell path hears nested branch creation and deletion', asy
|
|
|
424
360
|
assert.ok(created.refs.some((ref) => ref.name === branch));
|
|
425
361
|
|
|
426
362
|
await run(repo, 'branch', '-D', branch);
|
|
427
|
-
_private.ringDoorbell(before.replica);
|
|
428
363
|
deadline = Date.now() + 5_000;
|
|
429
364
|
let deleted = findCard(listRepoCards(), repo);
|
|
430
365
|
while (deleted.refs.some((ref) => ref.name === branch) && Date.now() < deadline) {
|
|
@@ -437,7 +372,7 @@ test('the targeted doorbell path hears nested branch creation and deletion', asy
|
|
|
437
372
|
assert.equal(deleted.generation, before.generation + 2);
|
|
438
373
|
});
|
|
439
374
|
|
|
440
|
-
test('the
|
|
375
|
+
test('the live service hears an uncommitted nested edit without a polling pass', async () => {
|
|
441
376
|
const repo = await makeRepo('projects/live-dirty');
|
|
442
377
|
const projects = path.join(tempRoot, 'projects');
|
|
443
378
|
upsertWorkspace({ path: projects, name: 'Projects', source: 'computer' });
|
|
@@ -448,8 +383,6 @@ test('the targeted doorbell path hears an uncommitted nested edit without a poll
|
|
|
448
383
|
const nested = path.join(repo, 'src', 'nested');
|
|
449
384
|
fs.mkdirSync(nested, { recursive: true });
|
|
450
385
|
fs.writeFileSync(path.join(nested, 'live.txt'), 'live\n');
|
|
451
|
-
const beforeCard = findCard(listRepoCards(), repo);
|
|
452
|
-
_private.ringDoorbell(beforeCard.replica);
|
|
453
386
|
|
|
454
387
|
const deadline = Date.now() + 5_000;
|
|
455
388
|
let card = findCard(listRepoCards(), repo);
|
|
@@ -486,7 +419,6 @@ test('editing an already-dirty file rings the checkpoint directly', async () =>
|
|
|
486
419
|
const card = findCard(listRepoCards(), repo);
|
|
487
420
|
const file = path.join(repo, 'dirty.txt');
|
|
488
421
|
fs.writeFileSync(file, 'first\n');
|
|
489
|
-
_private.ringDoorbell(card.replica);
|
|
490
422
|
|
|
491
423
|
let deadline = Date.now() + 5_000;
|
|
492
424
|
while (published.length === 0 && Date.now() < deadline) {
|
|
@@ -495,7 +427,6 @@ test('editing an already-dirty file rings the checkpoint directly', async () =>
|
|
|
495
427
|
published.length = 0;
|
|
496
428
|
|
|
497
429
|
fs.writeFileSync(file, 'second\n');
|
|
498
|
-
_private.ringDoorbell(card.replica);
|
|
499
430
|
deadline = Date.now() + 5_000;
|
|
500
431
|
while (published.length === 0 && Date.now() < deadline) {
|
|
501
432
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
@@ -13,18 +13,9 @@ const execFileAsync = promisify(execFile);
|
|
|
13
13
|
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-repo-cards-test-'));
|
|
14
14
|
process.env.AMALGM_DIR = path.join(tempRoot, '.amalgm');
|
|
15
15
|
process.env.AMALGM_COMPUTER_ID = 'computer-test';
|
|
16
|
-
fs.mkdirSync(process.env.AMALGM_DIR, { recursive: true });
|
|
17
|
-
fs.writeFileSync(
|
|
18
|
-
path.join(process.env.AMALGM_DIR, 'computer.json'),
|
|
19
|
-
JSON.stringify({
|
|
20
|
-
computer_id: 'computer-test',
|
|
21
|
-
user_id: '11111111-1111-4111-8111-111111111111',
|
|
22
|
-
}),
|
|
23
|
-
);
|
|
24
16
|
|
|
25
17
|
const { closeLocalDb, openLocalDb } = require('../state/db');
|
|
26
18
|
const {
|
|
27
|
-
_test: cardTest,
|
|
28
19
|
buildRepoCard,
|
|
29
20
|
cardFingerprint,
|
|
30
21
|
discoverWorktrees,
|
|
@@ -108,7 +99,7 @@ test('a clean repo cards with exact head, clean counts, and full identity', asyn
|
|
|
108
99
|
await run(dir, 'remote', 'add', 'origin', 'git@github.com:amalgm/clean.git');
|
|
109
100
|
|
|
110
101
|
const card = await buildRepoCard(dir);
|
|
111
|
-
assert.equal(card.card, 'repo-card@
|
|
102
|
+
assert.equal(card.card, 'repo-card@2');
|
|
112
103
|
assert.equal(card.machine, 'computer-test');
|
|
113
104
|
assert.match(card.replica, /^rpl_[A-Za-z0-9_-]+$/);
|
|
114
105
|
assert.equal(card.path, await fs.promises.realpath(dir));
|
|
@@ -408,82 +399,3 @@ test('the fingerprint holds across observations and moves on any real change', a
|
|
|
408
399
|
const third = await buildRepoCard(dir);
|
|
409
400
|
assert.notEqual(cardFingerprint(second), cardFingerprint(third));
|
|
410
401
|
});
|
|
411
|
-
|
|
412
|
-
test('unchanged commits reuse their card object source and one new oid is resolved once', async () => {
|
|
413
|
-
const dir = await makeRepo('object-source-cache');
|
|
414
|
-
await run(dir, 'branch', 'side');
|
|
415
|
-
const local = await buildRepoCard(dir);
|
|
416
|
-
await run(dir, 'remote', 'add', 'origin', 'https://github.com/amalgm/object-source-cache.git');
|
|
417
|
-
const { resourceId } = require('../workspace/wall').bindProject(
|
|
418
|
-
'018f0000-0000-7000-8000-00000000cace',
|
|
419
|
-
'11111111-1111-4111-8111-111111111111',
|
|
420
|
-
dir,
|
|
421
|
-
);
|
|
422
|
-
assert.equal(
|
|
423
|
-
require('../workspace/wall').projectForPath(dir)?.resourceId,
|
|
424
|
-
resourceId,
|
|
425
|
-
);
|
|
426
|
-
const ready = { kind: 'bundle', resourceId, checksum: 'ready' };
|
|
427
|
-
const previous = {
|
|
428
|
-
...local,
|
|
429
|
-
repo: { ...local.repo, remote: 'https://github.com/amalgm/object-source-cache' },
|
|
430
|
-
objects: ready,
|
|
431
|
-
refs: local.refs.map((ref) => ({ ...ref, objects: ready })),
|
|
432
|
-
};
|
|
433
|
-
const classified = await classifyDirectory(dir);
|
|
434
|
-
const replica = require('../workspace/identity').replicaForGitDir(
|
|
435
|
-
await fs.promises.realpath(classified.gitDir),
|
|
436
|
-
);
|
|
437
|
-
assert.equal(previous.replica, replica.replicaId);
|
|
438
|
-
assert.equal(
|
|
439
|
-
cardTest.reusableObjectSource(local.head.oid, local.head.oid, ready, resourceId),
|
|
440
|
-
ready,
|
|
441
|
-
);
|
|
442
|
-
const objects = require('../workspace/objects');
|
|
443
|
-
const original = objects.objectSourceFor;
|
|
444
|
-
let calls = 0;
|
|
445
|
-
objects.objectSourceFor = async () => {
|
|
446
|
-
calls += 1;
|
|
447
|
-
return { kind: 'pending' };
|
|
448
|
-
};
|
|
449
|
-
try {
|
|
450
|
-
const unchanged = await buildRepoCard(dir, previous);
|
|
451
|
-
assert.equal(calls, 0);
|
|
452
|
-
assert.equal(unchanged.objects.kind, 'bundle');
|
|
453
|
-
assert.ok(unchanged.refs.every((ref) => ref.objects.kind === 'bundle'));
|
|
454
|
-
|
|
455
|
-
write(dir, 'next.txt', 'next\n');
|
|
456
|
-
await run(dir, 'add', '.');
|
|
457
|
-
await run(dir, 'commit', '-m', 'next');
|
|
458
|
-
const changed = await buildRepoCard(dir, unchanged);
|
|
459
|
-
assert.equal(calls, 1, 'HEAD and its branch share one content-addressed lookup');
|
|
460
|
-
assert.equal(changed.objects.kind, 'pending');
|
|
461
|
-
assert.equal(changed.refs.find((ref) => ref.name === 'main').objects.kind, 'pending');
|
|
462
|
-
assert.equal(changed.refs.find((ref) => ref.name === 'side').objects.kind, 'bundle');
|
|
463
|
-
} finally {
|
|
464
|
-
objects.objectSourceFor = original;
|
|
465
|
-
}
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
test('only complete object sources are reusable', () => {
|
|
469
|
-
assert.deepEqual(
|
|
470
|
-
cardTest.reusableObjectSource(
|
|
471
|
-
'a',
|
|
472
|
-
'a',
|
|
473
|
-
{ kind: 'bundle', resourceId: 'shr_project_a', checksum: 'ready' },
|
|
474
|
-
'shr_project_a',
|
|
475
|
-
),
|
|
476
|
-
{ kind: 'bundle', resourceId: 'shr_project_a', checksum: 'ready' },
|
|
477
|
-
);
|
|
478
|
-
assert.equal(
|
|
479
|
-
cardTest.reusableObjectSource(
|
|
480
|
-
'a',
|
|
481
|
-
'a',
|
|
482
|
-
{ kind: 'bundle', resourceId: 'shr_project_old', checksum: 'ready' },
|
|
483
|
-
'shr_project_new',
|
|
484
|
-
),
|
|
485
|
-
null,
|
|
486
|
-
);
|
|
487
|
-
assert.equal(cardTest.reusableObjectSource('a', 'b', { kind: 'remote' }), null);
|
|
488
|
-
assert.equal(cardTest.reusableObjectSource('a', 'a', { kind: 'pending' }), null);
|
|
489
|
-
});
|
|
@@ -10,8 +10,6 @@ const { promisify } = require('util');
|
|
|
10
10
|
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
12
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-checkpoint-reducer-'));
|
|
13
|
-
process.env.AMALGM_DIR = path.join(root, '.amalgm');
|
|
14
|
-
process.env.AMALGM_WORKSPACES_DIR = path.join(process.env.AMALGM_DIR, 'workspaces');
|
|
15
13
|
const merge = require('../workspace/merge');
|
|
16
14
|
|
|
17
15
|
test.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
|
@@ -59,44 +57,51 @@ test('rail-ordered checkpoint transitions combine and a later revert removes onl
|
|
|
59
57
|
const treeA = await merge.treeOfPatch(repo, oid, patchA);
|
|
60
58
|
const treeB = await merge.treeOfPatch(repo, oid, patchB);
|
|
61
59
|
|
|
62
|
-
const afterA = await merge.reduceTree(repo, clean, clean, treeA);
|
|
63
|
-
const afterB = await merge.reduceTree(repo, afterA.tree, clean, treeB);
|
|
60
|
+
const afterA = await merge.reduceTree(repo, clean, clean, treeA, { label: '1' });
|
|
61
|
+
const afterB = await merge.reduceTree(repo, afterA.tree, clean, treeB, { label: '2' });
|
|
64
62
|
assert.equal(await treeText(repo, afterB.tree, 'a.txt'), 'a1\n');
|
|
65
63
|
assert.equal(await treeText(repo, afterB.tree, 'b.txt'), 'b1\n');
|
|
64
|
+
assert.deepEqual(afterB.conflicts, []);
|
|
66
65
|
|
|
67
66
|
// A's next operation says "from A+B to B": the reducer can remove A even
|
|
68
67
|
// though B's latest checkpoint already incorporated A.
|
|
69
|
-
const reverted = await merge.reduceTree(repo, afterB.tree, afterB.tree, treeB);
|
|
68
|
+
const reverted = await merge.reduceTree(repo, afterB.tree, afterB.tree, treeB, { label: '3' });
|
|
70
69
|
assert.equal(await treeText(repo, reverted.tree, 'a.txt'), 'a0\n');
|
|
71
70
|
assert.equal(await treeText(repo, reverted.tree, 'b.txt'), 'b1\n');
|
|
72
71
|
});
|
|
73
72
|
|
|
74
|
-
test('
|
|
75
|
-
const { repo, oid, tree: clean } = await makeRepo('
|
|
73
|
+
test('an overlapping transition produces deterministic conflict bytes', async () => {
|
|
74
|
+
const { repo, oid, tree: clean } = await makeRepo('conflict');
|
|
76
75
|
const leftPatch = await patchFor(repo, { 'same.txt': 'left\nline 2\n' });
|
|
77
76
|
const rightPatch = await patchFor(repo, { 'same.txt': 'right\nline 2\n' });
|
|
78
77
|
const left = await merge.treeOfPatch(repo, oid, leftPatch);
|
|
79
78
|
const right = await merge.treeOfPatch(repo, oid, rightPatch);
|
|
80
79
|
|
|
81
|
-
const current = await merge.reduceTree(repo, clean, clean, left);
|
|
82
|
-
const first = await merge.reduceTree(repo, current.tree, clean, right);
|
|
83
|
-
const second = await merge.reduceTree(repo, current.tree, clean, right);
|
|
80
|
+
const current = await merge.reduceTree(repo, clean, clean, left, { label: '10' });
|
|
81
|
+
const first = await merge.reduceTree(repo, current.tree, clean, right, { label: '11' });
|
|
82
|
+
const second = await merge.reduceTree(repo, current.tree, clean, right, { label: '11' });
|
|
84
83
|
|
|
85
84
|
assert.equal(first.tree, second.tree);
|
|
86
|
-
assert.
|
|
85
|
+
assert.deepEqual(first.conflicts, second.conflicts);
|
|
86
|
+
assert.equal(first.conflicts[0].kind, 'content-overlap');
|
|
87
|
+
const content = await treeText(repo, first.tree, 'same.txt');
|
|
88
|
+
assert.match(content, /<<<<<<< current/);
|
|
89
|
+
assert.match(content, /left/);
|
|
90
|
+
assert.match(content, /right/);
|
|
87
91
|
});
|
|
88
92
|
|
|
89
93
|
test('an echoed transition that is already present reduces to a no-op', async () => {
|
|
90
94
|
const { repo, oid, tree: clean } = await makeRepo('echo');
|
|
91
95
|
const patch = await patchFor(repo, { 'a.txt': 'shared\n' });
|
|
92
96
|
const shared = await merge.treeOfPatch(repo, oid, patch);
|
|
93
|
-
const current = await merge.reduceTree(repo, clean, clean, shared);
|
|
94
|
-
const echoed = await merge.reduceTree(repo, current.tree, clean, shared);
|
|
97
|
+
const current = await merge.reduceTree(repo, clean, clean, shared, { label: '20' });
|
|
98
|
+
const echoed = await merge.reduceTree(repo, current.tree, clean, shared, { label: '21' });
|
|
95
99
|
assert.equal(echoed.tree, current.tree);
|
|
100
|
+
assert.deepEqual(echoed.conflicts, []);
|
|
96
101
|
});
|
|
97
102
|
|
|
98
|
-
test('a
|
|
99
|
-
const { repo, oid, tree: clean } = await makeRepo('
|
|
103
|
+
test('a deletion never removes a file another writer edited', async () => {
|
|
104
|
+
const { repo, oid, tree: clean } = await makeRepo('delete-vs-edit');
|
|
100
105
|
const localPatch = await patchFor(repo, { 'a.txt': 'precious local edit\n' });
|
|
101
106
|
const localTree = await merge.treeOfPatch(repo, oid, localPatch);
|
|
102
107
|
fs.rmSync(path.join(repo, 'a.txt'));
|
|
@@ -104,44 +109,8 @@ test('a later deletion removes a file changed by an earlier writer', async () =>
|
|
|
104
109
|
await git(repo, 'reset', '--hard', 'HEAD');
|
|
105
110
|
const deletedTree = await merge.treeOfPatch(repo, oid, deletePatch);
|
|
106
111
|
|
|
107
|
-
const current = await merge.reduceTree(repo, clean, clean, localTree);
|
|
108
|
-
const outcome = await merge.reduceTree(repo, current.tree, clean, deletedTree);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
test('an earlier inbound event advances underneath a pending local write', async () => {
|
|
113
|
-
const { repo, oid, tree: clean } = await makeRepo('pending-local');
|
|
114
|
-
const inboundPatch = await patchFor(repo, { 'same.txt': 'inbound\n' });
|
|
115
|
-
const inbound = await merge.treeOfPatch(repo, oid, inboundPatch);
|
|
116
|
-
|
|
117
|
-
fs.writeFileSync(path.join(repo, 'same.txt'), 'pending local\n');
|
|
118
|
-
const outcome = await merge.advance(repo, clean, inbound);
|
|
119
|
-
|
|
120
|
-
assert.equal(fs.readFileSync(path.join(repo, 'same.txt'), 'utf8'), 'pending local\n');
|
|
121
|
-
assert.equal(await treeText(repo, outcome.heldTree, 'same.txt'), 'inbound\n');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('an inbound file write cannot overwrite a local write that races its observation', async () => {
|
|
125
|
-
const { repo, oid, tree: clean } = await makeRepo('pending-local-race');
|
|
126
|
-
const inboundPatch = await patchFor(repo, { 'same.txt': 'inbound\n' });
|
|
127
|
-
const inbound = await merge.treeOfPatch(repo, oid, inboundPatch);
|
|
128
|
-
const transition = require('../workspace/transition');
|
|
129
|
-
const applyFiles = transition.applyFiles;
|
|
130
|
-
transition.applyFiles = async (...args) => {
|
|
131
|
-
fs.writeFileSync(path.join(repo, 'same.txt'), 'local writer won the race\n');
|
|
132
|
-
return applyFiles(...args);
|
|
133
|
-
};
|
|
134
|
-
try {
|
|
135
|
-
await assert.rejects(
|
|
136
|
-
merge.advance(repo, clean, inbound),
|
|
137
|
-
/local file changed before ordered apply/,
|
|
138
|
-
);
|
|
139
|
-
} finally {
|
|
140
|
-
transition.applyFiles = applyFiles;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
assert.equal(
|
|
144
|
-
fs.readFileSync(path.join(repo, 'same.txt'), 'utf8'),
|
|
145
|
-
'local writer won the race\n',
|
|
146
|
-
);
|
|
112
|
+
const current = await merge.reduceTree(repo, clean, clean, localTree, { label: '30' });
|
|
113
|
+
const outcome = await merge.reduceTree(repo, current.tree, clean, deletedTree, { label: '31' });
|
|
114
|
+
assert.equal(outcome.conflicts[0].kind, 'delete-vs-change');
|
|
115
|
+
assert.equal(await treeText(repo, outcome.tree, 'a.txt'), 'precious local edit\n');
|
|
147
116
|
});
|