amalgm 0.1.265 → 0.1.266

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.265",
3
+ "version": "0.1.266",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -94,7 +94,10 @@ never merges, never decides truth — it emits facts for the layers above.
94
94
  (catch-up for the window we were dead), watcher death (rebuild the
95
95
  doorbell, reconcile the gap once), and whenever a consumer asks — a sync
96
96
  session opening, a document opening, an app regaining focus are events
97
- too, and they belong to the layer above.
97
+ too, and they belong to the layer above. A freshly registered repo is the
98
+ one adapter-defined birth exception: its Card + Checkpoint is already the
99
+ complete first observation, so it arms Watch without scanning every child
100
+ again. Restart still performs the ordinary conservative catch-up scan.
98
101
  4. **Echo suppression is content, not bookkeeping.** A writer records its own
99
102
  write (`noteWrite`); a scan that finds the recorded hash finds nothing.
100
103
  5. **Identity has two axes, never mixed — and KIND decides which axis
@@ -1229,6 +1229,22 @@ function createObserver(options) {
1229
1229
  }
1230
1230
  scanFilesRoot(root, { allowRootRepository: true });
1231
1231
  },
1232
+ /**
1233
+ * Structural registration already knows its nested Git boundaries from
1234
+ * the same state parcel that names them. Refresh only that in-memory
1235
+ * forwarding map after it installs those observer roots; this is not a
1236
+ * filesystem census and it neither reads nor records worktree files.
1237
+ */
1238
+ refreshRepoTerritories(rootId) {
1239
+ const root = store.listRoots().find((row) => row.rootId === rootId);
1240
+ if (!root || root.kind !== 'repo') {
1241
+ throw new Error(`refreshRepoTerritories requires a live repo root: ${rootId}`);
1242
+ }
1243
+ const state = live.get(root.rootId);
1244
+ if (!state) return;
1245
+ state.territories = new Set(childRepoRoots(root)
1246
+ .map((child) => child.path.slice(root.path.length + 1)));
1247
+ },
1232
1248
  scanNow,
1233
1249
 
1234
1250
  // The referee (verify.js): re-hash every enrolled byte, report every
@@ -1313,7 +1329,7 @@ function createObserver(options) {
1313
1329
  return statusOf(root);
1314
1330
  },
1315
1331
 
1316
- start(rootId = null) {
1332
+ start(rootId = null, { census = true } = {}) {
1317
1333
  const firstStart = !started;
1318
1334
  if (firstStart) started = true;
1319
1335
  if (!firstStart && rootId === null) return;
@@ -1328,10 +1344,19 @@ function createObserver(options) {
1328
1344
  const state = attach(root, { watch: true });
1329
1345
  state.held = false;
1330
1346
  }
1331
- try {
1332
- for (const root of roots) scanNow(root.rootId);
1333
- } catch (error) {
1334
- console.warn('[Observer] initial scan failed:', error?.message || error);
1347
+ // A normal root needs its first observed census before its structural
1348
+ // registration can become evidence. A freshly registered Git root is
1349
+ // different: its already-committed Card + Checkpoint is that complete
1350
+ // initial observation. Re-walking its worktree here would duplicate
1351
+ // the adapter's state (and can block the runtime for a large repo).
1352
+ // `census: false` is therefore only the repo adapter's birth path;
1353
+ // later recoveries and runtime start retain the conservative census.
1354
+ if (census) {
1355
+ try {
1356
+ for (const root of roots) scanNow(root.rootId);
1357
+ } catch (error) {
1358
+ console.warn('[Observer] initial scan failed:', error?.message || error);
1359
+ }
1335
1360
  }
1336
1361
  },
1337
1362
 
@@ -172,13 +172,14 @@ function createRegistration(options) {
172
172
  for (const { scopeId } of memory.allBindings()) {
173
173
  if (!liveIds.has(scopeId)) memory.dropScope(scopeId);
174
174
  }
175
- for (const [rootId] of [...lenses]) {
176
- const row = observer.listRoots().find((member) => member.rootId === rootId);
177
- if (!row) {
178
- lenses.delete(rootId);
179
- continue;
180
- }
181
- const record = rootRecordOf(rootId);
175
+ // Retirement is decided from durable witness bindings, not from the
176
+ // in-memory lens map. A fresh runtime has no lenses yet, but it may have
177
+ // restarted halfway through retiring an adopted tree; letting that old
178
+ // observer root survive would make its next start look like a second
179
+ // tree. The binding is the evidence that survives the crash, so it is
180
+ // the only honest starting point.
181
+ for (const row of observer.listRoots()) {
182
+ const record = rootRecordOf(row.rootId);
182
183
  const nestedRepoScope = row.kind === 'repo' && record?.type === 'repo.git'
183
184
  && observer.listRoots().some((other) => other.rootId !== row.rootId
184
185
  && row.path.startsWith(`${other.path}/`));
@@ -190,15 +191,18 @@ function createRegistration(options) {
190
191
  // sweep above collects. The reverse order once minted a duplicate
191
192
  // tree: an unbound living root looks like fresh ground.
192
193
  const family = observer.listRoots()
193
- .filter((member) => member.rootId === rootId || member.path.startsWith(`${row.path}/`));
194
+ .filter((member) => member.rootId === row.rootId || member.path.startsWith(`${row.path}/`));
194
195
  for (const member of family) {
195
- if (member.rootId !== rootId) observer.removeRoot(member.rootId);
196
+ if (member.rootId !== row.rootId) observer.removeRoot(member.rootId);
196
197
  }
197
- observer.removeRoot(rootId);
198
- memory.dropScope(rootId);
199
- lenses.delete(rootId);
198
+ observer.removeRoot(row.rootId);
199
+ memory.dropScope(row.rootId);
200
+ lenses.delete(row.rootId);
200
201
  }
201
202
  }
203
+ for (const [rootId] of [...lenses]) {
204
+ if (!observer.listRoots().some((row) => row.rootId === rootId)) lenses.delete(rootId);
205
+ }
202
206
  for (const root of treeRoots()) {
203
207
  if (lenses.has(root.rootId)) continue;
204
208
  const lens = createEvidence({ observer, registry, memory, rootId: root.rootId, classify });
@@ -508,18 +512,67 @@ function createRegistration(options) {
508
512
  throw error;
509
513
  }
510
514
 
511
- const startWatch = () => {
512
- observer.start(root.rootId);
513
- ensureTrees();
514
- reconcileInitialRepoWorktrees(root.path);
515
- flushIfDirty();
516
- requireLiveWatchers(root.path);
517
- };
515
+ const startWatch = () => startRepositoryFromBaseline(root);
518
516
  if (suppressJournal) registry.withoutJournal(startWatch);
519
517
  else startWatch();
520
518
  return { rootPath: root.path, record: rootRecordOf(root.rootId) };
521
519
  }
522
520
 
521
+ /**
522
+ * A repo's initial Card + Checkpoint is not preliminary data awaiting a
523
+ * generic filesystem census: it *is* the first complete state. Start its
524
+ * follower while the watcher is held, then release the watcher without
525
+ * scanning every worktree child a second time. The next raw ding reaches
526
+ * the follower and exactly one settled state authorizes the ordinary child
527
+ * reconciliation. This keeps Register synchronous without duplicating
528
+ * either its cargo capture or its initial Detect pass.
529
+ */
530
+ function startRepositoryFromBaseline(root) {
531
+ // Structural registration already knows every nested repo boundary from
532
+ // the same assignment that packed its state. Install those territories
533
+ // directly from that identity graph; discovering them again by walking
534
+ // files would reintroduce the duplicate initial census this adapter
535
+ // exists to avoid.
536
+ const territories = [];
537
+ for (const record of registry.activeByType('repo.git')) {
538
+ const repoPath = addressOf(record.uuid);
539
+ if (repoPath === null || (repoPath !== root.path && !repoPath.startsWith(`${root.path}/`))) continue;
540
+ let territory = observer.listRoots().find((candidate) => candidate.path === repoPath) ?? null;
541
+ if (!territory) territory = observer.enrollRoot(repoPath, { scan: false, watch: false });
542
+ if (territory.kind !== 'repo') {
543
+ throw new Error(`registered repository ${repoPath} is no longer Git ground before Watch began`);
544
+ }
545
+ memory.bind(territory.rootId, territory.rootId, record.uuid);
546
+ if (!lenses.has(territory.rootId)) {
547
+ // A repo baseline is already authoritative state. The lens exists so
548
+ // callers can address the tree, but does not reconcile absent
549
+ // observer child rows as if that state had never been captured.
550
+ lenses.set(territory.rootId, createEvidence({
551
+ observer, registry, memory, rootId: territory.rootId, classify,
552
+ }));
553
+ }
554
+ territories.push(territory);
555
+ }
556
+ territories.sort((left, right) => left.path.length - right.path.length);
557
+ // The outer raw watcher must forward a nested repo's first later ding
558
+ // to that nested follower. Its territory set is normally filled by a
559
+ // census; structural registration already supplied the same boundaries,
560
+ // so refresh the forwarding map without performing that census.
561
+ for (const territory of territories) observer.refreshRepoTerritories(territory.rootId);
562
+ repoFollowers.sync({ startOnBirth: false });
563
+ for (const territory of territories) observer.start(territory.rootId, { census: false });
564
+ requireLiveWatchers(root.path);
565
+
566
+ // Edge discovery is a distinct registration promise. It only reads link
567
+ // surfaces (repo roots answer that with a link census), never converts
568
+ // the just-packed worktree into generic per-file evidence. The newly
569
+ // reached roots then receive their usual identity/lens setup.
570
+ edgesDirty = true;
571
+ tryFollow();
572
+ ensureTrees();
573
+ repoFollowers.sync({ startOnBirth: false });
574
+ }
575
+
523
576
  /**
524
577
  * Plan all repository cargo under one selected root before its identity
525
578
  * transaction begins. This work can be slow (a Card contains a Git bundle
@@ -876,6 +929,7 @@ function createRegistration(options) {
876
929
  // mutation. Keep the whole first Watch/Detect transition inside the
877
930
  // existing journal boundary used for the seeded structural bind.
878
931
  const startWatch = () => {
932
+ if (root.kind === 'repo') return startRepositoryFromBaseline(root);
879
933
  observer.start(root.rootId);
880
934
  ensureTrees();
881
935
  reconcileInitialRepoWorktrees(root.path);
@@ -76,7 +76,16 @@ function createRepoFollowers({
76
76
  * move, be adopted, or cease being a repository; in every case the old
77
77
  * follower is closed before a replacement can speak.
78
78
  */
79
- function sync() {
79
+ /**
80
+ * A freshly registered repo already has a Card + Checkpoint baseline and
81
+ * its watcher is still held. Its follower must exist before that hold is
82
+ * released, but it must not immediately spend a second pair of full Git
83
+ * captures just to rediscover the state it was born with. Any ding heard
84
+ * during the release rings this follower normally. A cold runtime restart
85
+ * keeps the default (`startOnBirth: true`) and independently proves the
86
+ * persisted baseline against disk.
87
+ */
88
+ function sync({ startOnBirth = true } = {}) {
80
89
  if (stopped) return;
81
90
  const roots = observer.listRoots().filter((root) => root.kind === 'repo');
82
91
  const live = new Set(roots.map((root) => root.rootId));
@@ -143,9 +152,10 @@ function createRepoFollowers({
143
152
  },
144
153
  });
145
154
  followers.set(root.rootId, { path: root.path, uuid: record.uuid, follower });
146
- // Birth captures the initial Card + Checkpoint. The follower owns its
147
- // retry policy; registration must not invent an alternate Git scanner.
148
- void follower.start();
155
+ // A restart catches up from disk; a fresh Register has already
156
+ // captured its exact baseline and will receive held doorbells when
157
+ // Watch releases, so it does not duplicate the expensive birth pair.
158
+ if (startOnBirth) void follower.start();
149
159
  }
150
160
  }
151
161
 
@@ -23,7 +23,10 @@
23
23
  * although they have identical behavior today. A structural walk therefore
24
24
  * uses file.text as the content-free ordinary-file placeholder. The first
25
25
  * Detect pass immediately attests the actual bytes and corrects that
26
- * representation through the normal one-observation transition.
26
+ * representation through the first later settled repo reconciliation. The
27
+ * initial Card + Checkpoint is authoritative content state; repeating a
28
+ * generic per-file scan merely to refine this local physical label would
29
+ * duplicate that state and can freeze large registration.
27
30
  */
28
31
 
29
32
  const fs = require('fs');
@@ -622,7 +622,20 @@ function createEvidence(options) {
622
622
  const parentUuid = ensureDir(path.dirname(territory) === '.' ? '' : path.dirname(territory), root);
623
623
  const name = path.basename(territory);
624
624
  const at = registry.activeAt(parentUuid, name);
625
- if (at?.type !== 'repo.git') continue;
625
+ if (at?.type === 'repo.git') continue;
626
+ const territoryRoot = observer.listRoots()
627
+ .find((row) => row.kind === 'repo' && row.path === path.join(groundPath, territory));
628
+ const arrivedRecord = arrivedTree(territoryRoot?.device, territoryRoot?.inode, REPO_TREE);
629
+ if (!arrivedRecord) continue;
630
+ // A Git territory is the repo-shaped form of the same adoption law
631
+ // directories use above. Its initial Card + Checkpoint already
632
+ // travels with the existing repo UUID, so the new observer root must
633
+ // adopt that identity — never mint or ignore a half-repository.
634
+ if (at) {
635
+ nowDivergent.set(territory, null); // two identity claims: decide neither
636
+ continue;
637
+ }
638
+ registry.move(arrivedRecord.uuid, { parentUUID: parentUuid, name });
626
639
  }
627
640
  // A lifted lens is the reverse transition on the same record.
628
641
  for (const { record, rel } of walkActive(root.uuid, '', [])) {
@@ -750,6 +750,13 @@ function createRegistry(options) {
750
750
  /** Full logical graph for cloud snapshots. Machine bindings and evidence
751
751
  * are intentionally absent. */
752
752
  syncRecords: () => store.allEntities().map(syncRecord),
753
+ /** Active records of one physical type. This is a query primitive for
754
+ * type adapters; it does not expose storage writes or weaken the single
755
+ * registry boundary. */
756
+ activeByType: (type) => {
757
+ if (!TYPES.has(type)) throw new Error(`unknown entity type ${JSON.stringify(type)}`);
758
+ return store.activeByType(type).map(record);
759
+ },
753
760
  /** Every registered tree's top record. */
754
761
  roots: () => store.activeRoots().map(record),
755
762
  /** Every active managed reference with the identity it targets. The
@@ -608,13 +608,17 @@ test('adding a repository registers its allowed tree and saves its initial Repoc
608
608
  assert.ok(initialState?.stateId, 'registration saved the initial Card + Checkpoint');
609
609
  assert.ok(initialState.checkpoint.untracked.some((file) => file.path === 'readme.md'),
610
610
  'the persisted baseline contains the actual working checkpoint, not only its hash');
611
+ const observerRoot = service.observer.listRoots().find((root) => root.path === repo);
612
+ assert.deepEqual(service.observer.filesForRoot(observerRoot.rootId), [],
613
+ 'repo birth does not perform a second per-file census after its Card + Checkpoint is saved');
611
614
  assert.equal(tree.root().version, registered.version,
612
615
  'add returned only after the repo entity pointed at its saved baseline');
613
616
  const readme = tree.registry.activeAt(registered.uuid, 'readme.md');
614
617
  const src = tree.registry.activeAt(registered.uuid, 'src');
615
618
  assert.equal(readme.type, 'file.text');
616
619
  assert.equal(src.type, 'folder');
617
- assert.equal(tree.registry.activeAt(src.uuid, 'blob.bin').type, 'file.binary');
620
+ assert.equal(tree.registry.activeAt(src.uuid, 'blob.bin').type, 'file.text',
621
+ 'repo birth records structural leaf shape; the Card + Checkpoint owns its actual bytes without a second read');
618
622
  assert.equal(tree.registry.activeAt(registered.uuid, 'node_modules'), null,
619
623
  'the same enrollment policy excludes dependency trees inside repos');
620
624
  const initial = tree.root();