amalgm 0.1.262 → 0.1.263

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.
@@ -770,15 +770,21 @@ test('register registers every entity before it starts Watch', async () => {
770
770
  identity.close();
771
771
  assert.ok(nested, 'the folder already has a durable identity');
772
772
  assert.ok(file, 'so does the file within it');
773
- assert.equal(service.observer.status()[0].watching, false,
774
- 'the observer has not begun watching while registration is in progress');
773
+ // Watcher FIRST, conclusions LAST: by the time identity commits, the
774
+ // doorbell is already alive (it was proven before the walk began) —
775
+ // but HELD, so nothing has been observed or concluded yet. start()
776
+ // releasing the hold is the first moment Watch may speak.
777
+ assert.equal(service.observer.status()[0].watching, true,
778
+ 'the doorbell was born and proven before identity committed');
779
+ assert.equal(service.observer.filesForRoot(rootId).length, 0,
780
+ 'and held: no observation concluded while registration was in progress');
775
781
  checked = true;
776
782
  return start(rootId);
777
783
  };
778
784
 
779
785
  const { rootPath } = await service.register(ground);
780
786
  assert.equal(rootPath, ground);
781
- assert.equal(checked, true, 'Watch begins only after the structural walk commits');
787
+ assert.equal(checked, true, 'the held doorbell releases only after the structural walk commits');
782
788
  });
783
789
 
784
790
  test('register never reserves or mutates a user filename for watching', async () => {
@@ -911,13 +911,13 @@ test('add is one operation: a bare file registers, covered ground returns its tr
911
911
  'ground that would swallow a registered root refuses loudly');
912
912
  });
913
913
 
914
- test('add never claims success when a required watcher cannot open', () => {
914
+ test('add refuses before identity commits when a required watcher cannot be born', () => {
915
915
  const dir = makeDir('degraded-add');
916
916
  const observer = createObserver({
917
917
  db: new Database(':memory:'),
918
918
  emit: () => {},
919
919
  shouldEnroll: () => true,
920
- watch: () => ({ degraded: true, close: () => {} }),
920
+ watch: () => ({ degraded: true, reason: 'ENOSPC', close: () => {} }),
921
921
  });
922
922
  const identityDb = new Database(':memory:');
923
923
  const registration = createRegistration({
@@ -926,10 +926,14 @@ test('add never claims success when a required watcher cannot open', () => {
926
926
  classify: () => 'file.text',
927
927
  });
928
928
  try {
929
- assert.throws(() => registration.add(dir), /could not start required watcher/,
930
- 'a registered tree with no live doorbell is an incomplete add, never a success');
931
- assert.ok(registration.tree(dir)?.root(),
932
- 'identity remains durable for a later explicit retry; add only refuses to lie about Watch');
929
+ // Registration is synchronous, never optimistic: the watcher is proven
930
+ // FIRST, so a doorbell that cannot be born refuses with its reason and
931
+ // the remedy — and leaves NOTHING behind. No half-registered ghost, no
932
+ // local record the cloud never heard of.
933
+ assert.throws(() => registration.add(dir), /watcher could not be born \(ENOSPC\).*fs\.inotify\.max_user_watches/,
934
+ 'the refusal names the kernel budget and the fix');
935
+ assert.ok(!registration.tree(dir)?.root(), 'no identity row survives the refusal');
936
+ assert.equal(observer.listRoots().length, 0, 'no observer root survives it either — zero residue');
933
937
  } finally {
934
938
  registration.stop();
935
939
  observer.stop();
@@ -0,0 +1,182 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The warm checkpoint under exam: compute the state once, then let the
5
+ * doorbell keep it fresh — silence proves the written answer is still
6
+ * true, and a ding re-reads exactly what it named.
7
+ *
8
+ * The identity law above everything: a SCOPED capture's ids are
9
+ * byte-identical to a from-scratch capture's over the same ground.
10
+ * Scope changes what is READ, never what is TRUE.
11
+ */
12
+
13
+ const assert = require('node:assert/strict');
14
+ const { execFileSync } = require('child_process');
15
+ const fs = require('fs');
16
+ const os = require('os');
17
+ const path = require('path');
18
+ const test = require('node:test');
19
+
20
+ const { capture } = require('../repocard/capture');
21
+ const { createFollower } = require('../repocard/follow');
22
+
23
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-repocard-scope-'));
24
+ test.after(() => fs.rmSync(tempRoot, { recursive: true, force: true }));
25
+
26
+ function repo(name) {
27
+ const dir = fs.realpathSync(fs.mkdtempSync(path.join(tempRoot, `${name}-`)));
28
+ execFileSync('git', ['init', '-q'], { cwd: dir });
29
+ execFileSync('git', ['config', 'user.email', 'scope@amalgm.test'], { cwd: dir });
30
+ execFileSync('git', ['config', 'user.name', 'Scope'], { cwd: dir });
31
+ execFileSync('git', ['config', 'commit.gpgsign', 'false'], { cwd: dir });
32
+ return dir;
33
+ }
34
+
35
+ const previousOf = (snap) => snap.checkpoint.untracked.map(
36
+ ({ path: rel, mode, sha256 }) => ({ path: rel, mode, sha256 }),
37
+ );
38
+
39
+ test('a scoped capture is byte-identical to a from-scratch one — and reads less', () => {
40
+ const dir = repo('identity');
41
+ fs.writeFileSync(path.join(dir, 'kept.txt'), 'unchanged bytes');
42
+ fs.mkdirSync(path.join(dir, 'src'));
43
+ fs.writeFileSync(path.join(dir, 'src', 'edited.js'), 'v1');
44
+
45
+ const full = capture(dir, { withBundle: false });
46
+ fs.writeFileSync(path.join(dir, 'src', 'edited.js'), 'v2');
47
+
48
+ const scoped = capture(dir, {
49
+ withBundle: false,
50
+ suspects: ['src/edited.js'],
51
+ previousUntracked: previousOf(full),
52
+ });
53
+ const fresh = capture(dir, { withBundle: false });
54
+
55
+ assert.equal(scoped.stateId, fresh.stateId, 'scope changes what is read, never what is true');
56
+ assert.equal(scoped.checkpointId, fresh.checkpointId);
57
+ const kept = scoped.checkpoint.untracked.find((file) => file.path === 'kept.txt');
58
+ assert.equal(kept.reused, true, 'the unsuspected file was never opened');
59
+ assert.equal(kept.content, null);
60
+ assert.equal(kept.sha256, fresh.checkpoint.untracked.find((file) => file.path === 'kept.txt').sha256);
61
+ const edited = scoped.checkpoint.untracked.find((file) => file.path === 'src/edited.js');
62
+ assert.ok(!edited.reused, 'the suspect was read fresh');
63
+ assert.equal(edited.content.toString(), 'v2');
64
+ });
65
+
66
+ test('a directory-named suspect covers every file beneath it', () => {
67
+ const dir = repo('dir-suspect');
68
+ fs.mkdirSync(path.join(dir, 'notes'));
69
+ fs.writeFileSync(path.join(dir, 'notes', 'a.md'), 'a1');
70
+ fs.writeFileSync(path.join(dir, 'other.md'), 'other');
71
+ const full = capture(dir, { withBundle: false });
72
+
73
+ fs.writeFileSync(path.join(dir, 'notes', 'a.md'), 'a2');
74
+ const scoped = capture(dir, {
75
+ withBundle: false, suspects: ['notes'], previousUntracked: previousOf(full),
76
+ });
77
+ assert.equal(scoped.stateId, capture(dir, { withBundle: false }).stateId,
78
+ 'the OS may name only the folder; everything under it is suspect');
79
+ assert.ok(!scoped.checkpoint.untracked.find((file) => file.path === 'notes/a.md').reused);
80
+ assert.equal(scoped.checkpoint.untracked.find((file) => file.path === 'other.md').reused, true);
81
+ });
82
+
83
+ test('new and deleted untracked files are truth regardless of scope', () => {
84
+ const dir = repo('churn');
85
+ fs.writeFileSync(path.join(dir, 'stays.txt'), 'stays');
86
+ fs.writeFileSync(path.join(dir, 'leaves.txt'), 'leaves');
87
+ const full = capture(dir, { withBundle: false });
88
+
89
+ fs.rmSync(path.join(dir, 'leaves.txt'));
90
+ fs.writeFileSync(path.join(dir, 'arrives.txt'), 'new');
91
+ const scoped = capture(dir, {
92
+ withBundle: false, suspects: [], previousUntracked: previousOf(full),
93
+ });
94
+ assert.equal(scoped.stateId, capture(dir, { withBundle: false }).stateId,
95
+ 'git names the living; the previous list never resurrects the dead');
96
+ const paths = scoped.checkpoint.untracked.map((file) => file.path);
97
+ assert.ok(paths.includes('arrives.txt') && !paths.includes('leaves.txt'));
98
+ assert.ok(!scoped.checkpoint.untracked.find((file) => file.path === 'arrives.txt').reused,
99
+ 'a path the previous state never held is read, scoped or not');
100
+ });
101
+
102
+ test('bundle captures never reuse: cloud cargo reads every byte', () => {
103
+ const dir = repo('bundle');
104
+ fs.writeFileSync(path.join(dir, 'file.txt'), 'bytes');
105
+ fs.writeFileSync(path.join(dir, 'seed.txt'), 'seed');
106
+ execFileSync('git', ['add', 'seed.txt'], { cwd: dir });
107
+ execFileSync('git', ['commit', '-qm', 'seed'], { cwd: dir });
108
+ const full = capture(dir, { withBundle: false });
109
+
110
+ const bundled = capture(dir, {
111
+ withBundle: true, suspects: [], previousUntracked: previousOf(full),
112
+ });
113
+ const entry = bundled.checkpoint.untracked.find((file) => file.path === 'file.txt');
114
+ assert.ok(!entry.reused && entry.content, 'withBundle ignores scope by contract');
115
+ });
116
+
117
+ test('the follower scopes doorbell-driven pairs and re-suspects everything on flush', async () => {
118
+ const dir = repo('follower');
119
+ fs.writeFileSync(path.join(dir, 'a.txt'), 'a');
120
+ fs.writeFileSync(path.join(dir, 'b.txt'), 'b');
121
+
122
+ const seen = [];
123
+ const follower = createFollower({
124
+ repoPath: dir,
125
+ settleMs: 10,
126
+ emit: () => {},
127
+ captureFn: (repo, { suspects } = {}) => {
128
+ seen.push(suspects);
129
+ return capture(repo, { withBundle: false, suspects });
130
+ },
131
+ });
132
+
133
+ await follower.start();
134
+ assert.equal(seen[0], null, 'birth suspects everything — rings before birth were nobody\'s');
135
+ assert.equal(seen[1], null, 'both halves of the pair read the same scope');
136
+
137
+ seen.length = 0;
138
+ fs.writeFileSync(path.join(dir, 'a.txt'), 'a2');
139
+ follower.ring('a.txt');
140
+ await new Promise((resolve) => setTimeout(resolve, 150));
141
+ assert.deepEqual(seen, [['a.txt'], ['a.txt']],
142
+ 'a settled state narrows the next pair to exactly what rang');
143
+
144
+ seen.length = 0;
145
+ follower.ring('a.txt');
146
+ await new Promise((resolve) => setTimeout(resolve, 150));
147
+ assert.deepEqual(seen, [['a.txt'], ['a.txt']],
148
+ 'consumed hints stay consumed: only the new ring is suspect');
149
+
150
+ seen.length = 0;
151
+ await follower.flush();
152
+ assert.deepEqual(seen, [null, null], 'flush exists to catch lost doorbells: it never trusts the scope');
153
+
154
+ follower.close();
155
+ });
156
+
157
+ test('a nameless ring makes everything suspect until a pair settles', async () => {
158
+ const dir = repo('nameless');
159
+ fs.writeFileSync(path.join(dir, 'x.txt'), 'x');
160
+ const seen = [];
161
+ const follower = createFollower({
162
+ repoPath: dir,
163
+ settleMs: 10,
164
+ emit: () => {},
165
+ captureFn: (repo, { suspects } = {}) => {
166
+ seen.push(suspects);
167
+ return capture(repo, { withBundle: false, suspects });
168
+ },
169
+ });
170
+ await follower.start();
171
+ seen.length = 0;
172
+
173
+ follower.ring(null);
174
+ await new Promise((resolve) => setTimeout(resolve, 150));
175
+ assert.deepEqual(seen, [null, null], 'null means anywhere: the whole ground is suspect');
176
+
177
+ seen.length = 0;
178
+ follower.ring('x.txt');
179
+ await new Promise((resolve) => setTimeout(resolve, 150));
180
+ assert.deepEqual(seen, [['x.txt'], ['x.txt']], 'and the settle consumed the nameless suspicion');
181
+ follower.close();
182
+ });