amalgm 0.1.238 → 0.1.240

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.
Files changed (86) hide show
  1. package/lib/shared-realtime-tunnel.js +22 -14
  2. package/package.json +1 -1
  3. package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +68 -20
  4. package/runtime/scripts/amalgm-mcp/project-context/store.js +87 -19
  5. package/runtime/scripts/amalgm-mcp/state/attachments.js +40 -12
  6. package/runtime/scripts/amalgm-mcp/state/db.js +216 -17
  7. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +550 -0
  8. package/runtime/scripts/amalgm-mcp/state/docs.js +1101 -301
  9. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +99 -0
  10. package/runtime/scripts/amalgm-mcp/state/mutations.js +507 -130
  11. package/runtime/scripts/amalgm-mcp/state/promotions.js +21 -1
  12. package/runtime/scripts/amalgm-mcp/state/replicas.js +134 -13
  13. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +14 -8
  14. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +193 -0
  15. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +313 -94
  16. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +1 -0
  17. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +32 -17
  18. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +84 -0
  19. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +376 -13
  20. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +309 -0
  21. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +758 -30
  22. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +72 -3
  23. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +33 -6
  24. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +27 -0
  25. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +334 -191
  26. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +24 -0
  27. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +1999 -0
  28. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +364 -0
  29. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +81 -3
  30. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +114 -8
  31. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +185 -0
  32. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +174 -269
  33. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +26 -0
  34. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +2060 -407
  35. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +119 -0
  36. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +1446 -0
  37. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +5319 -3
  38. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +191 -1
  39. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +3140 -17
  40. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +135 -637
  41. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +5 -1
  42. package/runtime/scripts/amalgm-mcp/workspace/card.js +7 -7
  43. package/runtime/scripts/amalgm-mcp/workspace/cards.js +36 -12
  44. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +436 -173
  45. package/runtime/scripts/amalgm-mcp/workspace/content.js +32 -0
  46. package/runtime/scripts/amalgm-mcp/workspace/durable.js +45 -0
  47. package/runtime/scripts/amalgm-mcp/workspace/git.js +391 -37
  48. package/runtime/scripts/amalgm-mcp/workspace/intent.js +535 -0
  49. package/runtime/scripts/amalgm-mcp/workspace/merge.js +89 -6
  50. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +31 -0
  51. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  52. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  53. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +72 -0
  54. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +165 -0
  55. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +5 -0
  56. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +2 -0
  57. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +2201 -0
  58. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +19 -0
  59. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +18 -0
  60. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +18 -0
  61. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +33 -0
  62. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +47 -0
  63. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +43 -0
  64. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +114 -0
  65. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +84 -0
  66. package/runtime/scripts/amalgm-mcp/workspace/objects.js +119 -54
  67. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +616 -168
  68. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +240 -0
  69. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +1979 -570
  70. package/runtime/scripts/amalgm-mcp/workspace/rest.js +49 -28
  71. package/runtime/scripts/amalgm-mcp/workspace/store.js +77 -4
  72. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +240 -0
  73. package/runtime/scripts/amalgm-mcp/workspace/transition.js +1729 -192
  74. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +100 -0
  75. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +7 -1
  76. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +73 -61
  77. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +127 -0
  78. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +12 -0
  79. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +51 -6
  80. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +125 -0
  81. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +79 -8
  82. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +76 -0
  83. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +3480 -165
  84. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +3766 -0
  85. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +899 -71
  86. package/runtime/scripts/amalgm-mcp/workspace/wall.js +352 -497
@@ -52,7 +52,11 @@ function resolveAccess(nodeId, userIdInput, options = {}) {
52
52
  const database = options.database || openLocalDb();
53
53
  const userId = cleanUserId(userIdInput);
54
54
  const target = nodeRow(database, nodeId);
55
- if (!target || target.purged_at || target.missing_at) return null;
55
+ // `missing_at` is a durable observation that the physical node disappeared;
56
+ // it does not erase the UUID or its inherited authority. Reconciliation must
57
+ // still be able to authorize and journal that offline deletion. Purge alone
58
+ // retires the identity.
59
+ if (!target || target.purged_at) return null;
56
60
  const parents = new Map(
57
61
  database.prepare('SELECT id, parent_id FROM workspace_nodes WHERE workspace_id = ?')
58
62
  .all(target.workspace_id)
@@ -23,7 +23,7 @@ const {
23
23
  } = require('./git');
24
24
  const { cacheRootCommit, machineId, replicaForGitDir } = require('./identity');
25
25
 
26
- const CARD_CONTRACT = 'repo-card@2';
26
+ const CARD_CONTRACT = 'repo-card@3';
27
27
 
28
28
  // A transportable identity needs a dotted host: bare names resolve through
29
29
  // this machine's own config (ssh aliases, LAN names) and identify nothing
@@ -62,7 +62,6 @@ function cardKind(classified) {
62
62
 
63
63
  function reusableObjectSource(previousOid, currentOid, source, resourceId) {
64
64
  if (previousOid !== currentOid) return null;
65
- if (source?.kind === 'remote') return source;
66
65
  return source?.kind === 'bundle' && source.resourceId === resourceId ? source : null;
67
66
  }
68
67
 
@@ -83,8 +82,9 @@ async function observeWorktree(classified, previousCard = null) {
83
82
  }
84
83
 
85
84
  const repoIdentity = remote === null ? null : remoteIdentity(remote);
86
- const resourceId = repoIdentity
87
- ? require('./wall').projectForRepo(repoIdentity)?.resourceId
85
+ const repoKey = repoIdentity || (anchor ? `root:${anchor}` : null);
86
+ const resourceId = repoKey
87
+ ? require('./wall').projectForPath(worktreeRoot)?.resourceId
88
88
  : null;
89
89
  const previous = previousCard?.replica === replica.replicaId
90
90
  && previousCard.repo?.remote === repoIdentity
@@ -93,10 +93,10 @@ async function observeWorktree(classified, previousCard = null) {
93
93
  const previousRefs = new Map((previous?.refs || []).map((ref) => [ref.name, ref]));
94
94
  const objectSources = new Map();
95
95
  const sourceFor = (oid, priorOid, priorSource) => {
96
- if (!oid || !repoIdentity) return null;
96
+ if (!oid || !repoKey) return null;
97
97
  if (objectSources.has(oid)) return objectSources.get(oid);
98
98
  const source = reusableObjectSource(priorOid, oid, priorSource, resourceId)
99
- || require('./objects').objectSourceFor(worktreeRoot, repoIdentity, oid);
99
+ || require('./objects').objectSourceFor(worktreeRoot, repoKey, oid);
100
100
  objectSources.set(oid, source);
101
101
  return source;
102
102
  };
@@ -131,7 +131,7 @@ async function observeWorktree(classified, previousCard = null) {
131
131
  }
132
132
 
133
133
  /**
134
- * Build the repo-card@2 for one worktree. `dirPath` may be any directory
134
+ * Build the repo-card@3 for one worktree. `dirPath` may be any directory
135
135
  * inside the worktree; the card is always for the worktree root. Throws
136
136
  * on anything that is not a worktree — discovery filters first.
137
137
  *
@@ -37,7 +37,7 @@ const service = {
37
37
  reconcileTimer: null,
38
38
  workspaceTimer: null,
39
39
  unsubscribe: null,
40
- doorbells: new Map(), // replicaId → { watchers, timer }
40
+ doorbells: new Map(), // replicaId → { watchSet, timer }
41
41
  counters: { refreshes: 0, doorbellRings: 0 },
42
42
  lastRefresh: null,
43
43
  };
@@ -443,7 +443,7 @@ function nestedDirectories(root) {
443
443
  }
444
444
 
445
445
  function watchGitDir(card) {
446
- const watchers = [];
446
+ const watchSet = { handles: [], complete: true };
447
447
  const ring = () => ringDoorbell(card.replica);
448
448
  // The git dir itself carries HEAD, index, and operation markers; logs/
449
449
  // carries the reflog, which moves on every commit, checkout, and reset.
@@ -459,9 +459,13 @@ function watchGitDir(card) {
459
459
  for (const dir of new Set([...direct, ...refDirectories])) {
460
460
  try {
461
461
  const watcher = fs.watch(dir, ring);
462
- watcher.on('error', () => watcher.close());
463
- watchers.push(watcher);
462
+ watcher.on('error', () => {
463
+ watchSet.complete = false;
464
+ watcher.close();
465
+ });
466
+ watchSet.handles.push(watcher);
464
467
  } catch {
468
+ watchSet.complete = false;
465
469
  // The directory vanished between observation and watch; the next
466
470
  // reconcile pass re-syncs the bells.
467
471
  }
@@ -472,27 +476,31 @@ function watchGitDir(card) {
472
476
  if (relative === '.git' || relative.startsWith('.git/')) return;
473
477
  ring();
474
478
  });
475
- watcher.on('error', () => watcher.close());
476
- watchers.push(watcher);
479
+ watcher.on('error', () => {
480
+ watchSet.complete = false;
481
+ watcher.close();
482
+ });
483
+ watchSet.handles.push(watcher);
477
484
  } catch {
485
+ watchSet.complete = false;
478
486
  // The periodic card refresh remains the fallback on filesystems that
479
487
  // cannot watch a checkout recursively.
480
488
  }
481
- return watchers;
489
+ return watchSet;
482
490
  }
483
491
 
484
492
  function rebuildDoorbell(replicaId, card) {
485
493
  const bell = service.doorbells.get(replicaId);
486
494
  if (!bell) return;
487
- const previous = bell.watchers;
488
- bell.watchers = watchGitDir(card);
489
- for (const watcher of previous) watcher.close();
495
+ const previous = bell.watchSet;
496
+ for (const watcher of previous.handles) watcher.close();
497
+ bell.watchSet = watchGitDir(card);
490
498
  }
491
499
 
492
500
  function dropDoorbell(replicaId) {
493
501
  const bell = service.doorbells.get(replicaId);
494
502
  if (!bell) return;
495
- for (const watcher of bell.watchers) watcher.close();
503
+ for (const watcher of bell.watchSet.handles) watcher.close();
496
504
  if (bell.timer) clearTimeout(bell.timer);
497
505
  service.doorbells.delete(replicaId);
498
506
  }
@@ -507,7 +515,9 @@ function syncDoorbells(cards) {
507
515
  }
508
516
  for (const [replicaId, card] of healthy) {
509
517
  if (!service.doorbells.has(replicaId)) {
510
- service.doorbells.set(replicaId, { watchers: watchGitDir(card), timer: null });
518
+ service.doorbells.set(replicaId, { watchSet: watchGitDir(card), timer: null });
519
+ } else if (!service.doorbells.get(replicaId).watchSet.complete) {
520
+ rebuildDoorbell(replicaId, card);
511
521
  }
512
522
  }
513
523
  }
@@ -552,6 +562,8 @@ function repoCardServiceStats() {
552
562
  refreshes: service.counters.refreshes,
553
563
  doorbellRings: service.counters.doorbellRings,
554
564
  doorbells: service.doorbells.size,
565
+ incompleteDoorbells: Array.from(service.doorbells.values())
566
+ .filter((bell) => !bell.watchSet.complete).length,
555
567
  lastRefresh: service.lastRefresh,
556
568
  };
557
569
  }
@@ -563,4 +575,16 @@ module.exports = {
563
575
  repoCardServiceStats,
564
576
  startRepoCardService,
565
577
  stopRepoCardService,
578
+ // Test hooks only. Filesystem delivery itself belongs to the host; these
579
+ // expose the deterministic doorbell boundary behind that latency hint.
580
+ _private: {
581
+ rebuildDoorbell,
582
+ ringDoorbell,
583
+ watcherCount(replicaId) {
584
+ return service.doorbells.get(replicaId)?.watchSet.handles.length || 0;
585
+ },
586
+ watcherComplete(replicaId) {
587
+ return service.doorbells.get(replicaId)?.watchSet.complete === true;
588
+ },
589
+ },
566
590
  };