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.
Files changed (157) hide show
  1. package/README.md +12 -0
  2. package/lib/cli.js +146 -8
  3. package/lib/layout.js +89 -4
  4. package/lib/shared-realtime-tunnel.js +384 -43
  5. package/lib/supervisor.js +0 -3
  6. package/package.json +2 -2
  7. package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
  8. package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
  9. package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
  10. package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
  11. package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
  12. package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
  13. package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
  14. package/runtime/scripts/amalgm-mcp/index.js +4 -0
  15. package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
  16. package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
  17. package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
  18. package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
  19. package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
  20. package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
  21. package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
  22. package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
  23. package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
  24. package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
  25. package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
  26. package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
  27. package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
  28. package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
  29. package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
  30. package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
  31. package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
  32. package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
  33. package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
  34. package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
  35. package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
  36. package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
  37. package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
  38. package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
  39. package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
  40. package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
  41. package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
  42. package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
  43. package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
  44. package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
  45. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
  46. package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
  47. package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
  48. package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
  49. package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
  50. package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
  51. package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
  52. package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
  53. package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
  54. package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
  55. package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
  56. package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
  57. package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
  58. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
  59. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
  60. package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
  61. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
  62. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
  63. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
  64. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
  65. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
  66. package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
  67. package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
  68. package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
  69. package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
  70. package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
  71. package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
  72. package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
  73. package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
  74. package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
  75. package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
  76. package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
  77. package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
  78. package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
  79. package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
  80. package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
  81. package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
  82. package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
  83. package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
  84. package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
  85. package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
  86. package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
  87. package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
  88. package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
  89. package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
  90. package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
  91. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
  92. package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
  93. package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
  94. package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
  95. package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
  96. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
  97. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
  98. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
  99. package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
  100. package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
  101. package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
  102. package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
  103. package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
  104. package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
  105. package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
  106. package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
  107. package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
  108. package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
  109. package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
  110. package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
  111. package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
  112. package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
  113. package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
  114. package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
  115. package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
  116. package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
  117. package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
  118. package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
  119. package/runtime/scripts/local-gateway.js +1 -0
  120. package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
  121. package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
  122. package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
  123. package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
  124. package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
  125. package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
  126. package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
  127. package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
  128. package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
  129. package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
  130. package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
  131. package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
  132. package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
  133. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
  134. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
  135. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
  136. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
  137. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
  138. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
  139. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
  140. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
  141. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
  142. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
  143. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
  144. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
  145. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
  146. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
  147. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
  148. package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
  149. package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
  150. package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
  151. package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
  152. package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
  153. package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
  154. package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
  155. package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
  156. package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
  157. package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
@@ -0,0 +1,931 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The shared registration boundary (`amalgm add`) proving the composed
5
+ * story the audits demanded: every enrolled root exposes symlink edges
6
+ * regardless of its mutation adapter; watched ground is ALWAYS
7
+ * registered ground (a followed target gets a tree, a root record, a
8
+ * witness); a tree's identity is its permanent UUID and its path only
9
+ * the current address (moved ground is rescued, never re-minted); a
10
+ * link's derived resolution is the OS's own answer — realpath, chains
11
+ * and all — landing on the OWNING entity across every tree; exactly the
12
+ * named target enrolls (never an enclosing boundary the user did not
13
+ * name); and amalgm's enrollment policy gates every route, edges and
14
+ * explicit adds alike.
15
+ */
16
+
17
+ const test = require('node:test');
18
+ const assert = require('node:assert/strict');
19
+ const { execFileSync } = require('child_process');
20
+ const fs = require('fs');
21
+ const os = require('os');
22
+ const path = require('path');
23
+
24
+ const Database = require('better-sqlite3');
25
+ const { createObserver } = require('../observer');
26
+ const { createRegistration } = require('../registration');
27
+ const { createEvidenceMemory } = require('../registry/evidence');
28
+ const { shouldEnroll: defaultPolicy } = require('../workspace/tree/inclusion');
29
+ const { installFakeWatch } = require('./fake-watch');
30
+
31
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-registration-'));
32
+
33
+ test.after(() => {
34
+ fs.rmSync(tempRoot, { recursive: true, force: true });
35
+ });
36
+
37
+ function makeDir(name) {
38
+ const dir = fs.mkdtempSync(path.join(tempRoot, `${name}-`));
39
+ return fs.realpathSync(dir);
40
+ }
41
+
42
+ function gitInit(dir) {
43
+ execFileSync('git', ['init', '-q'], { cwd: dir });
44
+ }
45
+
46
+ /**
47
+ * One observer + one registration over the ONE in-memory identity
48
+ * database, events captured and routed. `restart()` builds a fresh
49
+ * boundary over the same observer and the same database — the
50
+ * re-attachment path.
51
+ */
52
+ function makeRig(options = {}) {
53
+ const events = [];
54
+ const ref = { current: null };
55
+ const observer = createObserver({
56
+ db: new Database(':memory:'),
57
+ emit: (event) => {
58
+ events.push(event);
59
+ ref.current?.onEvent(event);
60
+ },
61
+ shouldEnroll: options.shouldEnroll ?? (() => true),
62
+ });
63
+ // ONE identity database holds every tree; witness bindings key each
64
+ // tree by its permanent rootId, so a restart finds a moved tree by
65
+ // identity, never its address.
66
+ const identityDb = new Database(':memory:');
67
+ const boot = () => createRegistration({
68
+ observer, openIdentity: () => ({ db: identityDb, ephemeral: true }),
69
+ classify: () => 'file.text',
70
+ emit: (event) => events.push(event),
71
+ });
72
+ ref.current = boot();
73
+ return {
74
+ observer,
75
+ events,
76
+ identityDb,
77
+ get registration() { return ref.current; },
78
+ /** The crash window: events fire into a dead boundary. */
79
+ detach: () => { ref.current = null; },
80
+ restart: () => {
81
+ ref.current = boot();
82
+ return ref.current;
83
+ },
84
+ };
85
+ }
86
+
87
+ const committedSince = (events, from) => events.slice(from)
88
+ .filter((event) => event.type === 'entity.committed');
89
+
90
+ // --- the link record, proven against the composed system ------------------
91
+
92
+ test('a link record: the payload is the pointer as written; what it reaches is derived, and its death is honest', () => {
93
+ const rig = makeRig();
94
+ const dir = makeDir('link-rec');
95
+ fs.mkdirSync(path.join(dir, 'v2'));
96
+ fs.writeFileSync(path.join(dir, 'v2', 'app.js'), 'app');
97
+ fs.symlinkSync('v2', path.join(dir, 'current'));
98
+
99
+ const { rootPath, record: root } = rig.registration.add(dir);
100
+ assert.equal(rootPath, dir);
101
+ const tree = rig.registration.tree(dir);
102
+ const rootId = rig.observer.listRoots().find((row) => row.path === dir).rootId;
103
+ const link = tree.registry.activeAt(root.uuid, 'current');
104
+ const folderUuid = tree.registry.activeAt(root.uuid, 'v2').uuid;
105
+ assert.equal(link.type, 'link');
106
+ assert.equal(rig.registration.linkTargets().get(link.uuid), folderUuid,
107
+ 'derived: the written address currently reaches the real folder\'s record');
108
+
109
+ // The target folder moves; the pointer text did not. The OS calls the
110
+ // link broken — the record stays silent (its payload is the text), only
111
+ // the derived resolution dies, and amalgm never repairs the pointer by
112
+ // remembering what it used to reach.
113
+ fs.renameSync(path.join(dir, 'v2'), path.join(dir, 'v3'));
114
+ rig.observer.scanNow(rootId);
115
+ rig.registration.flushIfDirty();
116
+ assert.equal(tree.registry.activeAt(root.uuid, 'v3').uuid, folderUuid, 'the folder moved as itself');
117
+ assert.equal(tree.registry.activeAt(root.uuid, 'current').version, link.version,
118
+ 'the pointer text did not change, so the link record is silent');
119
+ assert.equal(rig.registration.linkTargets().get(link.uuid), null,
120
+ 'the derived resolution died honestly: amalgm remembers the text, not the target');
121
+
122
+ // A retarget IS the link's own change: the version moves with the text.
123
+ fs.unlinkSync(path.join(dir, 'current'));
124
+ fs.symlinkSync('v3', path.join(dir, 'current'));
125
+ rig.observer.scanNow(rootId);
126
+ rig.registration.flushIfDirty();
127
+ const retargeted = tree.registry.activeAt(root.uuid, 'current');
128
+ assert.equal(retargeted.uuid, link.uuid, 'same entity');
129
+ assert.notEqual(retargeted.version, link.version, 'a new written address is a new version');
130
+ assert.equal(rig.registration.linkTargets().get(link.uuid), folderUuid,
131
+ 'and the derived resolution lives again');
132
+
133
+ // A boundary restart re-attaches every tree; unchanged truth is silence.
134
+ const before = rig.events.length;
135
+ const reopened = rig.restart();
136
+ assert.deepEqual(committedSince(rig.events, before), [],
137
+ 'startup over unchanged truth is silence — link bindings included');
138
+ assert.equal(reopened.tree(dir).registry.activeAt(root.uuid, 'current').uuid, link.uuid);
139
+ });
140
+
141
+ // --- the four permanent composition exams ---------------------------------
142
+
143
+ test('a Git-contained link enrolls AND registers its external target', () => {
144
+ const rig = makeRig();
145
+ const outside = makeDir('git-edge-target');
146
+ fs.writeFileSync(path.join(outside, 'shared.md'), 'shared');
147
+ const repo = makeDir('git-edge');
148
+ gitInit(repo);
149
+ fs.symlinkSync(outside, path.join(repo, 'shared'));
150
+
151
+ const { record } = rig.registration.add(repo);
152
+ assert.equal(record.type, 'repo.git', 'the repo is one entity: its tree is the card\'s record');
153
+
154
+ const targetTree = rig.registration.tree(outside);
155
+ assert.ok(targetTree, 'the edge inside repo territory registered its target — a tree, not just a watcher');
156
+ const targetRoot = targetTree.root();
157
+ assert.equal(targetRoot.type, 'workspace');
158
+ assert.ok(targetTree.registry.activeAt(targetRoot.uuid, 'shared.md'),
159
+ 'the target\'s ground has records down to its files');
160
+ });
161
+
162
+ test('watched means registered: a followed target gets a tree, a witness, and resolution reaches its UUID', () => {
163
+ const rig = makeRig();
164
+ const outside = makeDir('reg-target');
165
+ fs.writeFileSync(path.join(outside, 'lib.js'), 'x');
166
+ const ws = makeDir('reg-ws');
167
+ fs.symlinkSync(outside, path.join(ws, 'dep'));
168
+
169
+ const { record: wsRoot } = rig.registration.add(ws);
170
+ const wsTree = rig.registration.tree(ws);
171
+ const link = wsTree.registry.activeAt(wsRoot.uuid, 'dep');
172
+ const targetTree = rig.registration.tree(outside);
173
+ assert.ok(targetTree, 'following an edge registers the target, never merely watches it');
174
+ assert.equal(rig.registration.linkTargets().get(link.uuid), targetTree.root().uuid,
175
+ 'the pointer resolves across trees to the target\'s own root UUID');
176
+ });
177
+
178
+ test('an external file target registers as a single-file tree, attested under the file laws', () => {
179
+ const rig = makeRig();
180
+ const holder = makeDir('solo-holder');
181
+ const filePath = path.join(holder, 'notes.md');
182
+ fs.writeFileSync(filePath, 'v1');
183
+ const ws = makeDir('solo-ws');
184
+ fs.symlinkSync(filePath, path.join(ws, 'notes'));
185
+
186
+ const { record: wsRoot } = rig.registration.add(ws);
187
+ const fileTree = rig.registration.tree(filePath);
188
+ assert.ok(fileTree, 'a file names itself: its tree is a tree of one');
189
+ const fileRoot = fileTree.root();
190
+ assert.equal(fileRoot.type, 'file.text');
191
+ assert.equal(fileRoot.name, 'notes.md');
192
+ const link = rig.registration.tree(ws).registry.activeAt(wsRoot.uuid, 'notes');
193
+ assert.equal(rig.registration.linkTargets().get(link.uuid), fileRoot.uuid,
194
+ 'resolution reaches the file entity itself');
195
+ assert.ok(!rig.observer.listRoots().some((root) => root.path === holder),
196
+ 'the parent was never enrolled: siblings the edge never named stay unshared');
197
+
198
+ // Content moves; the root record attests it like any file.
199
+ fs.writeFileSync(filePath, 'v2 — longer now');
200
+ const fileRootId = rig.observer.listRoots().find((row) => row.path === filePath).rootId;
201
+ rig.observer.scanNow(fileRootId);
202
+ rig.registration.flushIfDirty();
203
+ assert.notEqual(fileTree.root().version, fileRoot.version,
204
+ 'the single-file tree\'s version moved with its bytes');
205
+ });
206
+
207
+ test('a retarget automatically enrolls and registers the new ground; the old tree stays', () => {
208
+ const rig = makeRig();
209
+ const first = makeDir('auto-old');
210
+ const second = makeDir('auto-new');
211
+ fs.writeFileSync(path.join(second, 'fresh.md'), 'f');
212
+ const ws = makeDir('auto-ws');
213
+ fs.symlinkSync(first, path.join(ws, 'ptr'));
214
+
215
+ const { record: wsRoot } = rig.registration.add(ws);
216
+ assert.ok(rig.registration.tree(first), 'the first target registered at add time');
217
+
218
+ fs.unlinkSync(path.join(ws, 'ptr'));
219
+ fs.symlinkSync(second, path.join(ws, 'ptr'));
220
+ const wsRootId = rig.observer.listRoots().find((row) => row.path === ws).rootId;
221
+ rig.observer.scanNow(wsRootId); // link.changed marks the edges dirty through onEvent
222
+ rig.registration.flushIfDirty();
223
+
224
+ const secondTree = rig.registration.tree(second);
225
+ assert.ok(secondTree, 'the retarget registered the new ground without anyone asking');
226
+ const link = rig.registration.tree(ws).registry.activeAt(wsRoot.uuid, 'ptr');
227
+ assert.equal(rig.registration.linkTargets().get(link.uuid), secondTree.root().uuid);
228
+ assert.ok(rig.registration.tree(first),
229
+ 'the old tree stays — removing coverage is an explicit act, never a side effect');
230
+ });
231
+
232
+ // --- resolution is the OS's answer ----------------------------------------
233
+
234
+ test('chains resolve like the OS resolves: to the final real ground, in-tree and across trees', () => {
235
+ const rig = makeRig();
236
+ const dir = makeDir('chain');
237
+ fs.writeFileSync(path.join(dir, 'real.md'), 'r');
238
+ fs.symlinkSync('real.md', path.join(dir, 'hop'));
239
+ fs.symlinkSync('hop', path.join(dir, 'chain'));
240
+
241
+ const jumpHolder = makeDir('chain-mid');
242
+ const far = makeDir('chain-far');
243
+ fs.symlinkSync(far, path.join(jumpHolder, 'jump'));
244
+ fs.symlinkSync(path.join(jumpHolder, 'jump'), path.join(dir, 'out'));
245
+
246
+ const { record: root } = rig.registration.add(dir);
247
+ const tree = rig.registration.tree(dir);
248
+ const targets = rig.registration.linkTargets();
249
+ const fileUuid = tree.registry.activeAt(root.uuid, 'real.md').uuid;
250
+ assert.equal(targets.get(tree.registry.activeAt(root.uuid, 'chain').uuid), fileUuid,
251
+ 'chain → hop → real.md lands on the file, exactly where the OS lands');
252
+ assert.equal(targets.get(tree.registry.activeAt(root.uuid, 'hop').uuid), fileUuid);
253
+ assert.equal(targets.get(tree.registry.activeAt(root.uuid, 'out').uuid),
254
+ rig.registration.tree(far).root().uuid,
255
+ 'a cross-tree chain registers and resolves the FINAL ground');
256
+ assert.ok(!rig.registration.tree(jumpHolder),
257
+ 'intermediate ground was traversed, never registered: no edge named it as an endpoint');
258
+ });
259
+
260
+ test('repo-contained targets resolve to the owning entity: the card\'s record answers for its insides', () => {
261
+ const rig = makeRig();
262
+ const repo = makeDir('territory');
263
+ gitInit(repo);
264
+ fs.mkdirSync(path.join(repo, 'docs'));
265
+ fs.writeFileSync(path.join(repo, 'docs', 'a.md'), 'a');
266
+ const { record: repoRoot } = rig.registration.add(repo);
267
+ const ws = makeDir('territory-ws');
268
+ fs.symlinkSync(path.join(repo, 'docs'), path.join(ws, 'docs'));
269
+
270
+ const { record: wsRoot } = rig.registration.add(ws);
271
+ assert.equal(repoRoot.type, 'repo.git');
272
+ const link = rig.registration.tree(ws).registry.activeAt(wsRoot.uuid, 'docs');
273
+ assert.equal(rig.registration.linkTargets().get(link.uuid), repoRoot.uuid,
274
+ 'the repo is the owning sync entity, so its UUID answers for every address inside it — never a null that pretends no owner exists');
275
+
276
+ // A nested territory answers the same way: the repo.git record on the
277
+ // way down the tree owns everything beneath it.
278
+ const rig2 = makeRig();
279
+ const ws2 = makeDir('territory-outer');
280
+ const nested = path.join(ws2, 'vendor');
281
+ fs.mkdirSync(path.join(nested, 'lib'), { recursive: true });
282
+ gitInit(nested);
283
+ fs.writeFileSync(path.join(nested, 'lib', 'x.js'), 'x');
284
+ const other = makeDir('territory-other');
285
+ fs.symlinkSync(path.join(nested, 'lib'), path.join(other, 'lib'));
286
+
287
+ const { record: outerRoot } = rig2.registration.add(ws2);
288
+ const { record: otherRoot } = rig2.registration.add(other);
289
+ const nestedRecord = rig2.registration.tree(ws2).registry.activeAt(outerRoot.uuid, 'vendor');
290
+ assert.equal(nestedRecord.type, 'repo.git');
291
+ const link2 = rig2.registration.tree(other).registry.activeAt(otherRoot.uuid, 'lib');
292
+ assert.equal(rig2.registration.linkTargets().get(link2.uuid), nestedRecord.uuid,
293
+ 'a nested boundary\'s record owns its subtree');
294
+ });
295
+
296
+ // --- the four permanent exams of the second audit --------------------------
297
+
298
+ test('a moved target keeps its permanent UUID: identity survives the address', () => {
299
+ const rig = makeRig();
300
+ const holder = makeDir('moved-holder');
301
+ const A = path.join(holder, 'A');
302
+ fs.mkdirSync(A);
303
+ fs.writeFileSync(path.join(A, 'doc.md'), 'doc');
304
+ const ws = makeDir('moved-ws');
305
+ fs.symlinkSync(A, path.join(ws, 'ptr'));
306
+
307
+ rig.registration.add(ws);
308
+ const uuid = rig.registration.tree(A).root().uuid;
309
+ const docUuid = rig.registration.tree(A).registry.activeAt(uuid, 'doc.md').uuid;
310
+ const treeCount = rig.registration.trees().size;
311
+
312
+ const B = path.join(holder, 'B');
313
+ fs.renameSync(A, B);
314
+ fs.unlinkSync(path.join(ws, 'ptr'));
315
+ fs.symlinkSync(B, path.join(ws, 'ptr'));
316
+ const wsRootId = rig.observer.listRoots().find((row) => row.path === ws).rootId;
317
+ const before = rig.events.length;
318
+ rig.observer.scanNow(wsRootId);
319
+ rig.registration.flushIfDirty();
320
+ rig.registration.flushIfDirty(); // the rescue lands mid-follow; the second flush proves clean
321
+
322
+ const treeB = rig.registration.tree(B);
323
+ assert.ok(treeB, 'the tree lives at its new address');
324
+ assert.equal(treeB.root().uuid, uuid,
325
+ 'the UUID is the identity; the path was only the current local address');
326
+ assert.equal(treeB.registry.activeAt(uuid, 'doc.md').uuid, docUuid, 'records inside survived untouched');
327
+ assert.equal(rig.registration.tree(A), null, 'no ghost tree stayed behind at the old address');
328
+ assert.equal(rig.registration.trees().size, treeCount, 'a move minted nothing');
329
+ assert.deepEqual(
330
+ committedSince(rig.events, before).filter((event) => [uuid, docUuid].includes(event.record?.uuid)),
331
+ [], 'not one record in the moved tree was rewritten');
332
+ const wsTree = rig.registration.tree(ws);
333
+ const link = wsTree.registry.activeAt(wsTree.root().uuid, 'ptr');
334
+ assert.equal(rig.registration.linkTargets().get(link.uuid), uuid,
335
+ 'the retargeted pointer reaches the SAME entity it always did');
336
+
337
+ // The tree is fully alive at its new address: fresh ground reconciles.
338
+ fs.writeFileSync(path.join(B, 'later.md'), 'later');
339
+ rig.observer.scanNow(rig.observer.listRoots().find((row) => row.path === B).rootId);
340
+ rig.registration.flushIfDirty();
341
+ assert.ok(treeB.registry.activeAt(uuid, 'later.md'),
342
+ 'evidence follows the address: detection after the move lands on the same tree');
343
+ });
344
+
345
+ test('a restarted boundary finds a moved tree by its permanent identity, never its address', () => {
346
+ const rig = makeRig();
347
+ const holder = makeDir('restart-holder');
348
+ const A = path.join(holder, 'A');
349
+ fs.mkdirSync(A);
350
+ fs.writeFileSync(path.join(A, 'doc.md'), 'doc');
351
+ const ws = makeDir('restart-ws');
352
+ fs.symlinkSync(A, path.join(ws, 'ptr'));
353
+
354
+ rig.registration.add(ws);
355
+ const uuid = rig.registration.tree(A).root().uuid;
356
+
357
+ const B = path.join(holder, 'B');
358
+ fs.renameSync(A, B);
359
+ fs.unlinkSync(path.join(ws, 'ptr'));
360
+ fs.symlinkSync(B, path.join(ws, 'ptr'));
361
+ rig.observer.scanNow(rig.observer.listRoots().find((row) => row.path === ws).rootId);
362
+ rig.registration.flushIfDirty();
363
+ rig.registration.flushIfDirty(); // the rescue lands mid-follow; the second flush proves clean
364
+ assert.equal(rig.registration.tree(B).root().uuid, uuid, 'the live rescue held');
365
+
366
+ // The restart is the exam: durable storage opens by rootId, so the
367
+ // reopened boundary finds the SAME tree at the new address — a
368
+ // path-keyed policy would mint a fresh identity here.
369
+ const before = rig.events.length;
370
+ const reopened = rig.restart();
371
+ assert.equal(reopened.tree(B).root().uuid, uuid,
372
+ 'identity survived the restart: storage is keyed by the permanent rootId, never the address');
373
+ assert.deepEqual(committedSince(rig.events, before), [],
374
+ 'reopening a moved tree over unchanged truth is silence');
375
+ });
376
+
377
+ test('the policy judges the complete path: a benign name inside excluded ground is refused on every route', () => {
378
+ const rig = makeRig({ shouldEnroll: defaultPolicy });
379
+ const home = makeDir('sshhome');
380
+ fs.mkdirSync(path.join(home, '.ssh'));
381
+ const sshConfig = path.join(home, '.ssh', 'config');
382
+ fs.writeFileSync(sshConfig, 'Host *');
383
+ const ws = makeDir('sshws');
384
+ fs.writeFileSync(path.join(ws, 'app.md'), 'app');
385
+ fs.symlinkSync(sshConfig, path.join(ws, 'sshcfg'));
386
+
387
+ rig.registration.add(ws);
388
+ assert.ok(!rig.observer.listRoots().some((root) => root.path === sshConfig),
389
+ '"config" is a benign name, but .ssh on its path excludes it — never watched');
390
+ assert.equal(rig.registration.tree(sshConfig), null, 'and never registered');
391
+ assert.deepEqual(rig.registration.reported().map((entry) => [entry.path, entry.via]),
392
+ [[sshConfig, 'sshcfg']], 'the refusal is visible, never a silent basename bypass');
393
+ assert.match(rig.registration.reported()[0].reason, /enrollment policy/);
394
+
395
+ assert.throws(() => rig.registration.add(sshConfig), /enrollment policy excludes/,
396
+ 'the explicit route judges the same complete path');
397
+ });
398
+
399
+ test('an enrollment failure is never silence: the boundary stays dirty and the retry completes', () => {
400
+ const rig = makeRig();
401
+ const target = makeDir('loud-target');
402
+ fs.writeFileSync(path.join(target, 'lib.js'), 'x');
403
+ const ws = makeDir('loud-ws');
404
+ rig.registration.add(ws);
405
+
406
+ // One injected identity failure — a database error, not a vanished
407
+ // target. Swallowing it as disappearance would let the boundary call
408
+ // itself clean while the target silently stays unregistered.
409
+ const original = rig.observer.enrollRoot;
410
+ let failures = 1;
411
+ rig.observer.enrollRoot = (...args) => {
412
+ if (failures-- > 0) throw new Error('injected: identity store failure');
413
+ return original.apply(rig.observer, args);
414
+ };
415
+
416
+ fs.symlinkSync(target, path.join(ws, 'dep'));
417
+ rig.observer.scanNow(rig.observer.listRoots().find((row) => row.path === ws).rootId);
418
+ assert.equal(rig.registration.flushIfDirty(), false,
419
+ 'the failure propagated: the boundary stays dirty instead of reporting clean');
420
+ assert.equal(rig.registration.tree(target), null, 'nothing pretended to register');
421
+
422
+ assert.equal(rig.registration.flushIfDirty(), true, 'the retry runs the same pass and lands');
423
+ assert.ok(rig.registration.tree(target), 'the target is registered once the failure clears');
424
+ });
425
+
426
+ test('adding a symlink directly registers both the link and its target', () => {
427
+ const rig = makeRig();
428
+ const target = makeDir('alias-target');
429
+ fs.writeFileSync(path.join(target, 'x.md'), 'x');
430
+ const holder = makeDir('alias-holder');
431
+ const alias = path.join(holder, 'alias');
432
+ fs.symlinkSync(target, alias);
433
+
434
+ const { rootPath, record } = rig.registration.add(alias);
435
+ assert.equal(rootPath, alias, 'the address the user named is the ground that registers');
436
+ assert.equal(record.type, 'link', 'the pointer is an entity of its own — a tree of one');
437
+ assert.equal(record.name, 'alias');
438
+
439
+ const targetTree = rig.registration.tree(target);
440
+ assert.ok(targetTree, 'and its target followed as an edge, registered too');
441
+ assert.equal(rig.registration.linkTargets().get(record.uuid), targetTree.root().uuid,
442
+ 'two entities, connected: link UUID → target UUID');
443
+ assert.ok(!rig.observer.listRoots().some((root) => root.path === holder),
444
+ 'the holder was never enrolled: the alias names itself, not its parent');
445
+
446
+ // A retarget is the link entity's own change, and the resolution follows.
447
+ const second = makeDir('alias-second');
448
+ fs.unlinkSync(alias);
449
+ fs.symlinkSync(second, alias);
450
+ const aliasRootId = rig.observer.listRoots().find((row) => row.path === alias).rootId;
451
+ rig.observer.scanNow(aliasRootId);
452
+ rig.registration.flushIfDirty();
453
+ const after = rig.registration.tree(alias).root();
454
+ assert.equal(after.uuid, record.uuid, 'same entity');
455
+ assert.notEqual(after.version, record.version, 'a new written address is a new version');
456
+ assert.equal(rig.registration.linkTargets().get(record.uuid),
457
+ rig.registration.tree(second).root().uuid);
458
+ });
459
+
460
+ test('excluded targets are never auto-enrolled, and an explicit add refuses: one policy on every route', () => {
461
+ const rig = makeRig({ shouldEnroll: defaultPolicy });
462
+ const holder = makeDir('secret-holder');
463
+ const envPath = path.join(holder, '.env');
464
+ fs.writeFileSync(envPath, 'SECRET=1');
465
+ const ws = makeDir('secret-ws');
466
+ fs.writeFileSync(path.join(ws, 'app.md'), 'app');
467
+ fs.symlinkSync(envPath, path.join(ws, 'config'));
468
+
469
+ const { record: wsRoot } = rig.registration.add(ws);
470
+ const link = rig.registration.tree(ws).registry.activeAt(wsRoot.uuid, 'config');
471
+ assert.ok(link, 'the link record itself is workspace truth and stays');
472
+ assert.ok(!rig.observer.listRoots().some((root) => root.path === envPath),
473
+ 'the secret was never watched');
474
+ assert.equal(rig.registration.tree(envPath), null, 'and never registered');
475
+ assert.deepEqual(rig.registration.reported().map((entry) => [entry.path, entry.via]),
476
+ [[envPath, 'config']], 'the refusal is visible on the boundary, never silent');
477
+ assert.match(rig.registration.reported()[0].reason, /enrollment policy/);
478
+ assert.equal(rig.registration.linkTargets().get(link.uuid), null,
479
+ 'resolution is honestly null: no entity owns excluded ground');
480
+
481
+ assert.throws(() => rig.registration.add(envPath), /enrollment policy excludes/,
482
+ 'the explicit route asks the same policy — the deliberate override is future work, never a default');
483
+ });
484
+
485
+ // --- adoption: a tree boundary is not identity ------------------------------
486
+
487
+ test('a registered tree moving inside another registered tree is ADOPTED: same UUID, new parent, descendants silent', () => {
488
+ const rig = makeRig();
489
+ const ws = makeDir('adopt-ws');
490
+ const outside = makeDir('adopt-target');
491
+ fs.writeFileSync(path.join(outside, 'doc.md'), 'doc');
492
+ fs.symlinkSync(outside, path.join(ws, 'ptr'));
493
+
494
+ rig.registration.add(ws);
495
+ const treeUuid = rig.registration.tree(outside).root().uuid;
496
+ const docUuid = rig.registration.tree(outside).registry.activeAt(treeUuid, 'doc.md').uuid;
497
+
498
+ // The reproduced cross-tree move: the separately registered target
499
+ // relocates INSIDE the workspace, and the link follows it there.
500
+ const newHome = path.join(ws, 'target');
501
+ fs.renameSync(outside, newHome);
502
+ fs.unlinkSync(path.join(ws, 'ptr'));
503
+ fs.symlinkSync(newHome, path.join(ws, 'ptr'));
504
+ const before = rig.events.length;
505
+ rig.observer.scanNow();
506
+ rig.registration.flushIfDirty();
507
+ rig.registration.flushIfDirty(); // the second pass retires the orphan's observer family
508
+
509
+ const wsTree = rig.registration.tree(ws);
510
+ const inside = wsTree.registry.activeAt(wsTree.root().uuid, 'target');
511
+ assert.equal(inside.uuid, treeUuid,
512
+ 'a tree boundary is not identity: the UUID rode the move into the new tree');
513
+ assert.equal(inside.type, 'folder',
514
+ 'and the root shape shed in the same transition — workspace is the root type only');
515
+ assert.equal(wsTree.registry.activeAt(treeUuid, 'doc.md').uuid, docUuid,
516
+ 'every descendant rode along: the arrival law reconnected its channel to the same record');
517
+ assert.equal(committedSince(rig.events, before)
518
+ .filter((event) => event.record?.uuid === docUuid).length, 0,
519
+ 'descendants moved without one record rewritten');
520
+ assert.equal(rig.registration.tree(outside), null, 'no stranded ghost tree at the old address');
521
+ assert.ok(!rig.observer.listRoots().some((root) => root.path === outside),
522
+ 'the orphan\'s observer family retired: the ground is covered by its new tree');
523
+ const link = wsTree.registry.activeAt(wsTree.root().uuid, 'ptr');
524
+ assert.equal(rig.registration.linkTargets().get(link.uuid), treeUuid,
525
+ 'the retargeted pointer reaches the SAME entity it always did — inside its new tree');
526
+
527
+ // And the adoption is durable: a restarted boundary sees the same graph.
528
+ const mark = rig.events.length;
529
+ const reopened = rig.restart();
530
+ assert.deepEqual(committedSince(rig.events, mark), [], 'restart over the adopted graph is silence');
531
+ assert.equal(reopened.tree(ws).registry.activeAt(reopened.tree(ws).root().uuid, 'target').uuid, treeUuid);
532
+ });
533
+
534
+ test('a registered repository moving inside a registered workspace adopts as territory: same UUID, repo.git child', () => {
535
+ const rig = makeRig();
536
+ const repo = makeDir('adopt-repo');
537
+ gitInit(repo);
538
+ fs.writeFileSync(path.join(repo, 'lib.js'), 'x');
539
+ const { record: repoRecord } = rig.registration.add(repo);
540
+ const ws = makeDir('adopt-repo-ws');
541
+ rig.registration.add(ws);
542
+
543
+ fs.renameSync(repo, path.join(ws, 'vendor'));
544
+ rig.observer.scanNow();
545
+ rig.registration.flushIfDirty();
546
+ rig.registration.flushIfDirty();
547
+
548
+ const wsTree = rig.registration.tree(ws);
549
+ const vendor = wsTree.registry.activeAt(wsTree.root().uuid, 'vendor');
550
+ assert.equal(vendor.uuid, repoRecord.uuid,
551
+ 'the repo tree adopted as territory of its new tree — never a fresh territory record');
552
+ assert.equal(vendor.type, 'repo.git');
553
+ assert.equal(rig.registration.tree(repo), null, 'no stranded tree at the old address');
554
+ assert.ok(!rig.observer.listRoots().some((root) => root.path === repo),
555
+ 'the orphan repo root retired; the enclosed territory root carries the watching');
556
+ });
557
+
558
+ test('a retirement torn at any point never resurrects a duplicate: restart completes it', () => {
559
+ const rig = makeRig();
560
+ const ws = makeDir('tear-ws');
561
+ fs.writeFileSync(path.join(ws, 'w.md'), 'w');
562
+ const outside = makeDir('tear-target');
563
+ fs.writeFileSync(path.join(outside, 'doc.md'), 'doc');
564
+ const wsUuid = rig.registration.add(ws).record.uuid;
565
+ const tgtUuid = rig.registration.add(outside).record.uuid;
566
+ const orphanRootId = rig.observer.listRoots().find((root) => root.path === outside).rootId;
567
+ rig.registration.flushIfDirty();
568
+
569
+ fs.renameSync(outside, path.join(ws, 'target'));
570
+ rig.observer.scanNow();
571
+ rig.registration.flushIfDirty(); // the adoption commits
572
+
573
+ // TEAR ONE — the worst one: the process dies before a single observer
574
+ // row is removed, leaving a living orphan family. Retirement is
575
+ // evidence-first, identity-last, so the family's witness binding still
576
+ // stands and still proves the adoption. (The reverse order once
577
+ // resurrected a duplicate here: an unbound living root looks like
578
+ // fresh ground, and fresh ground mints.)
579
+ const realRemove = rig.observer.removeRoot;
580
+ rig.observer.removeRoot = () => { throw new Error('injected: process died mid-retirement'); };
581
+ try { rig.registration.flushIfDirty(); } catch { /* the crash */ }
582
+ rig.observer.removeRoot = realRemove;
583
+
584
+ const mark = rig.events.length;
585
+ const reborn = rig.restart();
586
+ assert.deepEqual([...reborn.trees().values()].map((tree) => tree.root().uuid), [wsUuid],
587
+ 'one tree after the restart: nothing minted over the orphan');
588
+ assert.equal(reborn.tree(ws).registry.activeAt(wsUuid, 'target').uuid, tgtUuid,
589
+ 'the adopted UUID is intact');
590
+ assert.ok(!rig.observer.listRoots().some((root) => root.path === outside),
591
+ 'the restart completed the torn retirement');
592
+ assert.deepEqual(committedSince(rig.events, mark), [], 'and completing it committed nothing');
593
+
594
+ // TEAR TWO — rows removed, scope never dropped. That exact state is
595
+ // constructed here; the residue sweep collects it at the next pass,
596
+ // because a scope whose witness root no longer lives is a finished
597
+ // retirement's leftovers (the loss law has already vouched for every
598
+ // living tree before the sweep runs).
599
+ const second = makeDir('tear-second');
600
+ fs.writeFileSync(path.join(second, 's.md'), 's');
601
+ const secondUuid = reborn.add(second).record.uuid;
602
+ const secondRootId = rig.observer.listRoots().find((root) => root.path === second).rootId;
603
+ reborn.flushIfDirty();
604
+ fs.renameSync(second, path.join(ws, 'second'));
605
+ rig.observer.scanNow();
606
+ reborn.flushIfDirty(); // adoption
607
+ rig.observer.removeRoot(secondRootId); // the crash left exactly this: rows gone, scope standing
608
+ reborn.flushIfDirty();
609
+ const memory = createEvidenceMemory(rig.identityDb);
610
+ assert.equal(memory.allBindings().filter((row) => row.scopeId === secondRootId).length, 0,
611
+ 'the sweep dropped the dead scope: no residue');
612
+ assert.equal(memory.allBindings().filter((row) => row.scopeId === orphanRootId).length, 0,
613
+ 'tear one\'s scope is gone too');
614
+ assert.equal(reborn.tree(ws).registry.activeAt(wsUuid, 'second').uuid, secondUuid,
615
+ 'the second adoption held through its tear as well');
616
+ });
617
+
618
+ test('a REAL restart: file-backed databases, closed and reopened, prove durable identity end to end', () => {
619
+ const stateDir = makeDir('durable-state');
620
+ const observerDbPath = path.join(stateDir, 'observer.db');
621
+ const identityDbPath = path.join(stateDir, 'identity.db');
622
+ const sentinelPath = path.join(stateDir, 'identity.epoch');
623
+ const events = [];
624
+
625
+ // A boot is the real thing: fresh connections over the durable files.
626
+ const boot = () => {
627
+ const observerDb = new Database(observerDbPath);
628
+ const identityDb = new Database(identityDbPath);
629
+ const ref = { current: null };
630
+ const observer = createObserver({
631
+ db: observerDb,
632
+ emit: (event) => { events.push(event); ref.current?.onEvent(event); },
633
+ });
634
+ ref.current = createRegistration({
635
+ observer, openIdentity: () => ({ db: identityDb, sentinelPath }),
636
+ classify: () => 'file.text',
637
+ emit: (event) => events.push(event),
638
+ });
639
+ return {
640
+ observer,
641
+ registration: ref.current,
642
+ close: () => { observer.stop(); observerDb.close(); identityDb.close(); },
643
+ };
644
+ };
645
+
646
+ // Session one: register a workspace whose link reaches an external
647
+ // tree, move that tree (rescued live), and lose a second tree's ground
648
+ // (honest absence). Then CLOSE EVERYTHING — no shared handles survive.
649
+ const one = boot();
650
+ const ws = makeDir('durable-ws');
651
+ const holder = makeDir('durable-holder');
652
+ const A = path.join(holder, 'A');
653
+ fs.mkdirSync(A);
654
+ fs.writeFileSync(path.join(A, 'doc.md'), 'doc');
655
+ fs.symlinkSync(A, path.join(ws, 'ptr'));
656
+ const gone = makeDir('durable-gone');
657
+ fs.writeFileSync(path.join(gone, 'x.md'), 'x');
658
+
659
+ one.registration.add(ws);
660
+ one.registration.add(gone);
661
+ const movedUuid = one.registration.tree(A).root().uuid;
662
+ const goneUuid = one.registration.tree(gone).root().uuid;
663
+
664
+ const B = path.join(holder, 'B');
665
+ fs.renameSync(A, B);
666
+ fs.unlinkSync(path.join(ws, 'ptr'));
667
+ fs.symlinkSync(B, path.join(ws, 'ptr'));
668
+ fs.rmSync(gone, { recursive: true, force: true });
669
+ one.observer.scanNow();
670
+ one.registration.flushIfDirty();
671
+ one.registration.flushIfDirty();
672
+ assert.equal(one.registration.tree(B).root().uuid, movedUuid, 'the live rescue held');
673
+ one.close();
674
+
675
+ // Session two: a true process-restart shape. Identity, presence, and
676
+ // bindings must all come back from the files alone — and unchanged
677
+ // truth must reconcile in silence.
678
+ const mark = events.length;
679
+ const two = boot();
680
+ assert.equal(two.registration.tree(B).root().uuid, movedUuid,
681
+ 'the moved tree came back by identity from durable storage — same UUID, new address');
682
+ assert.equal(two.registration.tree(gone).root().uuid, goneUuid,
683
+ 'the absent tree\'s identity survived the restart too');
684
+ const goneRow = two.observer.listRoots().find((row) => row.path === gone);
685
+ assert.equal(goneRow.present, false,
686
+ 'presence is durable evidence: the restart still knows this ground is unavailable');
687
+ assert.ok(two.observer.verify(goneRow.rootId).length > 0, 'and the referee still convicts it');
688
+ assert.deepEqual(events.slice(mark).filter((event) => event.type === 'entity.committed'), [],
689
+ 'reopening over unchanged truth is silence — no record minted, moved, or rewritten');
690
+ two.close();
691
+ });
692
+
693
+ test('the loss law convicts at the boundary: a fresh observer store over surviving trees mints nothing', () => {
694
+ const identityDb = new Database(':memory:');
695
+ const observer = createObserver({ db: new Database(':memory:'), emit: () => {} });
696
+ const registration = createRegistration({
697
+ observer, openIdentity: () => ({ db: identityDb, ephemeral: true }),
698
+ classify: () => 'file.text', emit: () => {},
699
+ });
700
+ const ws = makeDir('loss-ws');
701
+ fs.writeFileSync(path.join(ws, 'a.md'), 'a');
702
+ const external = makeDir('loss-external');
703
+ fs.writeFileSync(path.join(external, 'lib.md'), 'lib');
704
+ fs.symlinkSync(external, path.join(ws, 'ptr'));
705
+ registration.add(ws);
706
+
707
+ // The observer store is LOST; the surviving identity database still
708
+ // holds the tree. A fresh boundary must convict loudly at startup —
709
+ // BEFORE it acts at all: no lens mints a replacement over the
710
+ // surviving records, and no edge is followed onto new ground. A
711
+ // convicted boundary never expands coverage.
712
+ const observer2 = createObserver({ db: new Database(':memory:'), emit: () => {} });
713
+ observer2.enrollRoot(ws);
714
+ assert.throws(() => createRegistration({
715
+ observer: observer2, openIdentity: () => ({ db: identityDb, ephemeral: true }),
716
+ classify: () => 'file.text', emit: () => {},
717
+ }), /identity evidence lost/, 'a lost store is never a fresh start');
718
+ assert.equal(observer2.listRoots().some((root) => root.path === external), false,
719
+ 'conviction came before action: the edge was never followed onto new ground');
720
+ });
721
+
722
+ // --- the managed reference: the OTHER link kind -----------------------------
723
+
724
+ test('a managed reference targets a UUID, not a path: the rendering follows the target through absence and reattachment', () => {
725
+ const rig = makeRig();
726
+ const home = makeDir('ref-home'); // the Amalgm folder: ordinary registered ground
727
+ rig.registration.add(home);
728
+ const holder = makeDir('ref-holder');
729
+ const project = path.join(holder, 'project');
730
+ fs.mkdirSync(project);
731
+ fs.writeFileSync(path.join(project, 'main.js'), 'x');
732
+
733
+ const { record: ref, targetUUID } = rig.registration.addReference(project, path.join(home, 'project'));
734
+ assert.equal(ref.type, 'reference');
735
+ assert.equal(targetUUID, rig.registration.tree(project).root().uuid,
736
+ 'the payload is the target entity\'s identity, never a path');
737
+ assert.equal(fs.readlinkSync(path.join(home, 'project')), project,
738
+ 'locally the reference renders as an ordinary symlink to the target\'s current address');
739
+ rig.registration.flushIfDirty();
740
+ assert.equal(rig.registration.linkTargets().get(ref.uuid), targetUUID,
741
+ 'the rendering resolves to the target: reference UUID → target UUID');
742
+
743
+ // The target moves while nothing observes it: honest absence first,
744
+ // then the reattachment moment rescues it — and the derived rendering
745
+ // follows. Neither UUID ever changes; the reference record never moves.
746
+ const moved = path.join(holder, 'project-v2');
747
+ fs.renameSync(project, moved);
748
+ rig.observer.scanNow();
749
+ rig.observer.enrollRoot(moved); // the reattachment names the new address
750
+ rig.registration.flushIfDirty();
751
+ assert.equal(fs.readlinkSync(path.join(home, 'project')), moved,
752
+ 'the rendering follows the target: rewritten by the boundary, repaired after absence');
753
+ assert.equal(rig.registration.tree(moved).root().uuid, targetUUID, 'same target entity');
754
+ const after = rig.registration.tree(home).registry.record(ref.uuid);
755
+ assert.equal(after.version, ref.version,
756
+ 'a rendering rewrite is derived state: the reference record itself never changed');
757
+ rig.registration.flushIfDirty();
758
+ assert.equal(rig.registration.linkTargets().get(ref.uuid), targetUUID,
759
+ 'resolution lives again at the new address');
760
+ });
761
+
762
+ test('a reference is canonical truth with an idempotent projection: it self-heals, retries converge, real ground is never overwritten', async () => {
763
+ const rig = makeRig();
764
+ const home = makeDir('heal-home');
765
+ fs.writeFileSync(path.join(home, 'keep.md'), 'k');
766
+ rig.registration.add(home);
767
+ const homeRootId = rig.observer.listRoots().find((root) => root.path === home).rootId;
768
+ const notes = path.join(makeDir('heal-holder'), 'notes.md');
769
+ fs.writeFileSync(notes, 'n');
770
+ const at = path.join(home, 'notes');
771
+
772
+ // The rendering fails AFTER the record commits. The record is
773
+ // canonical truth; the deliberate act is idempotent, so repeating it
774
+ // converges the projection instead of refusing its own slot.
775
+ const realSymlink = fs.symlinkSync;
776
+ fs.symlinkSync = () => { throw new Error('injected: symlink failed after the record committed'); };
777
+ assert.throws(() => rig.registration.addReference(notes, at), /injected/);
778
+ fs.symlinkSync = realSymlink;
779
+ const { record: ref, targetUUID } = rig.registration.addReference(notes, at);
780
+ assert.equal(fs.readlinkSync(at), notes, 'the repeated act converged the missing projection');
781
+ assert.equal(rig.registration.tree(home).registry.references().length, 1, 'one record, not two');
782
+
783
+ // A vanished rendering is recreated by the ordinary flush: missing
784
+ // projections are exactly what the projector recreates — which is also
785
+ // why a rewrite may be remove-then-render and still never tear.
786
+ fs.unlinkSync(at);
787
+ rig.observer.scanNow(homeRootId);
788
+ rig.registration.flushIfDirty();
789
+ assert.equal(fs.readlinkSync(at), notes, 'a missing projection is recreated');
790
+
791
+ // ... and by the EVENT-scheduled pass: maintenance is automatic after
792
+ // events, never only on an explicit ask.
793
+ fs.unlinkSync(at);
794
+ rig.observer.scanNow(homeRootId); // the deletion fact schedules the full flush
795
+ await new Promise((resolve) => setImmediate(() => setImmediate(resolve)));
796
+ assert.equal(fs.readlinkSync(at), notes, 'the scheduled flush projected it back');
797
+
798
+ // Real ground occupying the slot is DIVERGENCE, never overwritten —
799
+ // and the ground leaving the address resolves it, like any contest.
800
+ fs.unlinkSync(at);
801
+ fs.writeFileSync(at, 'user data');
802
+ rig.observer.scanNow(homeRootId);
803
+ rig.registration.flushIfDirty();
804
+ assert.equal(fs.readFileSync(at, 'utf8'), 'user data', 'the projector never overwrites real ground');
805
+ assert.ok(rig.registration.tree(home).evidence.divergence().has('notes'),
806
+ 'the lens reports the occupied rendering slot');
807
+ fs.unlinkSync(at);
808
+ rig.observer.scanNow(homeRootId);
809
+ rig.registration.flushIfDirty();
810
+ assert.equal(fs.readlinkSync(at), notes, 'the projection returns when the ground leaves');
811
+ rig.registration.flushIfDirty(); // the recreated rendering's channel binds on the next pass
812
+ assert.equal(rig.registration.tree(home).registry.record(ref.uuid).version, ref.version,
813
+ 'through every repair the reference record never changed');
814
+ assert.equal(rig.registration.linkTargets().get(ref.uuid), targetUUID);
815
+ });
816
+
817
+ test('startup is the same convergence pass: a rendering no event can ever heal is recreated at attachment', () => {
818
+ const rig = makeRig();
819
+ const home = makeDir('startup-home');
820
+ rig.registration.add(home);
821
+ const target = path.join(makeDir('startup-holder'), 'notes.md');
822
+ fs.writeFileSync(target, 'n');
823
+ const rendered = path.join(home, 'notes');
824
+ const { record: ref, targetUUID } = rig.registration.addReference(target, rendered);
825
+ assert.equal(fs.readlinkSync(rendered), target);
826
+
827
+ // The worst shape: the deletion is DETECTED — the event fires into a
828
+ // dead boundary (the crash window) — so the store already holds this
829
+ // truth. The catch-up scan after restart finds equal truth: silence.
830
+ // No event will ever fire for it again; only startup itself can heal.
831
+ const homeRootId = rig.observer.listRoots().find((root) => root.path === home).rootId;
832
+ rig.detach();
833
+ fs.unlinkSync(rendered);
834
+ rig.observer.scanNow(homeRootId);
835
+ assert.equal(fs.existsSync(rendered), false, 'nothing attached, nothing healed');
836
+
837
+ rig.restart();
838
+ assert.equal(fs.readlinkSync(rendered), target,
839
+ 'attachment recreated the rendering — no event, no explicit flush, the startup pass itself');
840
+ rig.observer.scanNow(homeRootId); // the catch-up over healed truth stays silent
841
+ rig.registration.flushIfDirty(); // the recreated rendering's channel binds on the next pass
842
+ assert.equal(fs.readlinkSync(rendered), target);
843
+ assert.equal(rig.registration.tree(home).registry.record(ref.uuid).version, ref.version,
844
+ 'healing is projection, never mutation: the reference record is untouched');
845
+ assert.equal(rig.registration.linkTargets().get(ref.uuid), targetUUID);
846
+ });
847
+
848
+ test('a managed reference follows its single-file target renamed in place: the rendering re-aims live', async () => {
849
+ // The user-level promise, end to end through the doorbell: the parent
850
+ // hears the rename (lawful worst case — only the new name arrives),
851
+ // the file root's channel inode claims the new address, root.moved
852
+ // schedules the full flush, and the projection follows. No
853
+ // unavailable moment, no explicit act.
854
+ const { deliver, restore } = installFakeWatch();
855
+ try {
856
+ const rig = makeRig();
857
+ const home = makeDir('aim-home');
858
+ rig.registration.add(home);
859
+ const holder = makeDir('aim-holder');
860
+ const A = path.join(holder, 'A.md');
861
+ fs.writeFileSync(A, 'alpha');
862
+ const rendered = path.join(home, 'note');
863
+ rig.registration.addReference(A, rendered);
864
+ assert.equal(fs.readlinkSync(rendered), A);
865
+ rig.observer.start();
866
+ try {
867
+ const B = path.join(holder, 'B.md');
868
+ fs.renameSync(A, B);
869
+ deliver(holder, 'B.md');
870
+ await new Promise((resolve) => setImmediate(() => setImmediate(resolve)));
871
+ assert.equal(fs.readlinkSync(rendered), B, 'the rendering follows the renamed file');
872
+ assert.equal(rig.events.some((event) => event.type === 'root.unavailable'), false,
873
+ 'continuity never broke: the follow is live, not an absence-then-repair');
874
+ } finally {
875
+ rig.observer.stop();
876
+ }
877
+ } finally {
878
+ restore();
879
+ }
880
+ });
881
+
882
+ // --- the add surface -------------------------------------------------------
883
+
884
+ test('add is one operation: a bare file registers, covered ground returns its tree, a swallow refuses', () => {
885
+ const rig = makeRig();
886
+ const holder = makeDir('add-holder');
887
+ const ws = path.join(holder, 'ws');
888
+ fs.mkdirSync(ws);
889
+ fs.writeFileSync(path.join(ws, 'a.md'), 'a');
890
+ const filePath = path.join(makeDir('add-file'), 'todo.md');
891
+ fs.writeFileSync(filePath, 't');
892
+
893
+ const solo = rig.registration.add(filePath);
894
+ assert.equal(solo.record.type, 'file.text', '`amalgm add <file>` is the same primitive: a tree of one');
895
+
896
+ const added = rig.registration.add(ws);
897
+ const again = rig.registration.add(path.join(ws, 'a.md'));
898
+ assert.equal(again.rootPath, ws, 'ground already inside a registered tree returns that tree');
899
+ assert.equal(again.record.uuid, added.record.uuid);
900
+
901
+ assert.throws(() => rig.registration.add(holder), /one owner per path/,
902
+ 'ground that would swallow a registered root refuses loudly');
903
+ });
904
+
905
+ test('the would-swallow edge refusal is visible on the boundary, never silent', () => {
906
+ const rig = makeRig();
907
+ const holder = makeDir('report-holder');
908
+ const ws = path.join(holder, 'ws');
909
+ fs.mkdirSync(ws);
910
+ fs.symlinkSync(holder, path.join(ws, 'up'));
911
+
912
+ rig.registration.add(ws);
913
+ assert.deepEqual(rig.registration.reported().map((entry) => [entry.path, entry.via]),
914
+ [[holder, 'up']], 'the refused edge is reported with the pointer that raised it');
915
+ });
916
+
917
+ test('an inconclusive edge follow keeps the boundary dirty until the ground settles', () => {
918
+ const rig = makeRig();
919
+ const dir = makeDir('sealed-follow');
920
+ gitInit(dir);
921
+ fs.mkdirSync(path.join(dir, 'sealed'));
922
+ fs.chmodSync(path.join(dir, 'sealed'), 0o000);
923
+ try {
924
+ rig.registration.add(dir);
925
+ assert.equal(rig.registration.flushIfDirty(), false,
926
+ 'unreadable ground may hide an edge — the follow concludes no completeness');
927
+ } finally {
928
+ fs.chmodSync(path.join(dir, 'sealed'), 0o755);
929
+ }
930
+ assert.equal(rig.registration.flushIfDirty(), true, 'the settled ground converges clean');
931
+ });