amalgm 0.1.245 → 0.1.246
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/lib/cli.js +146 -8
- package/lib/layout.js +89 -4
- package/lib/shared-realtime-tunnel.js +384 -43
- package/lib/supervisor.js +0 -3
- package/package.json +2 -2
- package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
- package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
- package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
- package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
- package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
- package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
- package/runtime/scripts/amalgm-mcp/index.js +4 -0
- package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
- package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
- package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
- package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
- package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
- package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
- package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
- package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
- package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
- package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
- package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
- package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
- package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
- package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
- package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
- package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
- package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
- package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
- package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
- package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
- package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
- package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
- package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
- package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
- package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
- package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
- package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
- package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
- package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
- package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
- package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
- package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
|
@@ -394,42 +394,27 @@ test('workspace removal drops the project_context row', () => {
|
|
|
394
394
|
assert.equal(store.getProjectContext(workspace.path, { reconcile: false }), null);
|
|
395
395
|
});
|
|
396
396
|
|
|
397
|
-
test('
|
|
397
|
+
test('watcher reconciles direct disk edits', async () => {
|
|
398
398
|
const { projectPath, workspace } = makeProject('watch');
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
fs.watch = (...args) => {
|
|
402
|
-
watchAttempts += 1;
|
|
403
|
-
if (watchAttempts === 1) {
|
|
404
|
-
const error = new Error('simulated dropped watcher');
|
|
405
|
-
error.code = 'EMFILE';
|
|
406
|
-
throw error;
|
|
407
|
-
}
|
|
408
|
-
return originalWatch.apply(fs, args);
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
try {
|
|
412
|
-
store.ensureProjectContext(workspace);
|
|
413
|
-
const before = store.getProjectContext(projectPath, { reconcile: false }).contextRevision;
|
|
399
|
+
store.ensureProjectContext(workspace);
|
|
400
|
+
const before = store.getProjectContext(projectPath, { reconcile: false }).contextRevision;
|
|
414
401
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
402
|
+
fs.writeFileSync(
|
|
403
|
+
path.join(projectPath, '.amalgm/context/project-state.md'),
|
|
404
|
+
'# Project State\n\nWatched edit.\n',
|
|
405
|
+
);
|
|
419
406
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
assert.notEqual(row.contextRevision, before, 'scheduled verification updated the row');
|
|
428
|
-
assert.ok(row.prompt.projectState.includes('Watched edit.'));
|
|
429
|
-
assert.ok(watchAttempts >= 2, 'the scheduled pass retried the failed watcher');
|
|
430
|
-
} finally {
|
|
431
|
-
fs.watch = originalWatch;
|
|
407
|
+
// fs.watch + 200ms debounce; poll the row instead of guessing exact timing.
|
|
408
|
+
const deadline = Date.now() + 3000;
|
|
409
|
+
let row = null;
|
|
410
|
+
while (Date.now() < deadline) {
|
|
411
|
+
row = store.getProjectContext(projectPath, { reconcile: false });
|
|
412
|
+
if (row && row.contextRevision !== before) break;
|
|
413
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
432
414
|
}
|
|
415
|
+
assert.ok(row, 'row exists');
|
|
416
|
+
assert.notEqual(row.contextRevision, before, 'watcher updated the row');
|
|
417
|
+
assert.ok(row.prompt.projectState.includes('Watched edit.'));
|
|
433
418
|
});
|
|
434
419
|
|
|
435
420
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The production-owned vertical slice:
|
|
5
|
+
*
|
|
6
|
+
* amalgm add [path] → POST /entities/add → registration.add(path)
|
|
7
|
+
* → Watch + Detect + Record
|
|
8
|
+
*
|
|
9
|
+
* The wall already proves detection. What is unproven here is the
|
|
10
|
+
* WIRING — the parts that only exist because production owns the stack:
|
|
11
|
+
* which policies are actually injected, where state lands, that the
|
|
12
|
+
* observer's events genuinely reach the boundary, that identity
|
|
13
|
+
* survives a process restart over the same directory, and that the
|
|
14
|
+
* endpoint reports refusal as refusal.
|
|
15
|
+
*
|
|
16
|
+
* Everything runs over a temporary AMALGM_DIR, set before config loads,
|
|
17
|
+
* so the singleton this route uses can never touch a real installation.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const test = require('node:test');
|
|
24
|
+
const assert = require('node:assert');
|
|
25
|
+
|
|
26
|
+
const tempRoot = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-add-')));
|
|
27
|
+
process.env.AMALGM_DIR = path.join(tempRoot, 'home');
|
|
28
|
+
|
|
29
|
+
const { classifyFile } = require('../registration/classify');
|
|
30
|
+
const { ENTITIES_DIR, createRegistrationService, registrationService } = require('../registration/service');
|
|
31
|
+
const { handleEntityRoutes } = require('../server/routes/entities');
|
|
32
|
+
const { createLocalServiceRouter } = require('../server/local-service-router');
|
|
33
|
+
|
|
34
|
+
const services = [];
|
|
35
|
+
|
|
36
|
+
test.after(() => {
|
|
37
|
+
for (const service of services) service.stop();
|
|
38
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function makeService(name) {
|
|
42
|
+
const service = createRegistrationService({ dir: path.join(tempRoot, `state-${name}`) });
|
|
43
|
+
services.push(service);
|
|
44
|
+
return service;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A directory of real ground, outside the Amalgm home. */
|
|
48
|
+
function makeGround(name) {
|
|
49
|
+
const dir = path.join(tempRoot, `ground-${name}`);
|
|
50
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
51
|
+
return fs.realpathSync(dir);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- the injected type policy ---------------------------------------------
|
|
55
|
+
|
|
56
|
+
test('classify: ordinary bytes are text', () => {
|
|
57
|
+
const file = path.join(makeGround('classify-text'), 'notes.md');
|
|
58
|
+
fs.writeFileSync(file, '# hello\nplain words\n');
|
|
59
|
+
assert.equal(classifyFile(file), 'file.text');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('classify: a NUL in the opening bytes means bytes, never text', () => {
|
|
63
|
+
const file = path.join(makeGround('classify-binary'), 'blob.bin');
|
|
64
|
+
fs.writeFileSync(file, Buffer.from([0x89, 0x50, 0x00, 0x4e, 0x47]));
|
|
65
|
+
assert.equal(classifyFile(file), 'file.binary');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('classify: ground that cannot be read supports no structural claim', () => {
|
|
69
|
+
// The weaker of the two answers, never a throw: the caller is
|
|
70
|
+
// mid-transaction over a hash it already holds.
|
|
71
|
+
assert.equal(classifyFile(path.join(tempRoot, 'never-existed')), 'file.binary');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('classify: a NUL past the sniff window does not retroactively unmake text', () => {
|
|
75
|
+
// The law is about the OPENING bytes, stated exactly so a huge text
|
|
76
|
+
// file is never re-read whole to answer a hint.
|
|
77
|
+
const file = path.join(makeGround('classify-late'), 'long.txt');
|
|
78
|
+
fs.writeFileSync(file, Buffer.concat([Buffer.alloc(9000, 0x61), Buffer.from([0])]));
|
|
79
|
+
assert.equal(classifyFile(file), 'file.text');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// --- composition: what production actually builds --------------------------
|
|
83
|
+
|
|
84
|
+
test('the service composes one observer and one boundary over one directory', () => {
|
|
85
|
+
const service = makeService('compose');
|
|
86
|
+
assert.ok(fs.existsSync(path.join(service.dir, 'observer.db')), 'evidence database');
|
|
87
|
+
assert.ok(fs.existsSync(path.join(service.dir, 'identity.db')), 'identity database');
|
|
88
|
+
assert.ok(fs.existsSync(path.join(service.dir, 'identity.epoch')), 'the identity sentinel');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('the singleton lives under the Amalgm home, and is one', () => {
|
|
92
|
+
assert.equal(ENTITIES_DIR, path.join(process.env.AMALGM_DIR, 'entities'));
|
|
93
|
+
assert.equal(registrationService(), registrationService(), 'built once, returned forever');
|
|
94
|
+
services.push(registrationService());
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('add registers the named ground and every file beneath it', () => {
|
|
98
|
+
const service = makeService('add');
|
|
99
|
+
const ground = makeGround('add');
|
|
100
|
+
fs.writeFileSync(path.join(ground, 'a.txt'), 'alpha');
|
|
101
|
+
fs.mkdirSync(path.join(ground, 'nested'));
|
|
102
|
+
fs.writeFileSync(path.join(ground, 'nested', 'b.bin'), Buffer.from([0, 1, 2]));
|
|
103
|
+
|
|
104
|
+
const { rootPath, record } = service.add(ground);
|
|
105
|
+
assert.equal(rootPath, ground);
|
|
106
|
+
assert.ok(record.uuid, 'the tree has a permanent identity');
|
|
107
|
+
assert.equal(record.parentUUID, null, 'a tree is a parentless record');
|
|
108
|
+
|
|
109
|
+
const registry = service.registration.tree(ground).registry;
|
|
110
|
+
const named = new Map(registry.children(record.uuid).map((row) => [row.name, row]));
|
|
111
|
+
assert.deepEqual([...named.keys()].sort(), ['a.txt', 'nested']);
|
|
112
|
+
assert.equal(named.get('a.txt').type, 'file.text', 'the real classifier ran');
|
|
113
|
+
|
|
114
|
+
const nested = registry.children(named.get('nested').uuid);
|
|
115
|
+
assert.equal(nested.length, 1);
|
|
116
|
+
assert.equal(nested[0].type, 'file.binary', 'and it distinguishes bytes from text');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('watching is armed: add begins the Watch rail, not just the walk', () => {
|
|
120
|
+
const service = makeService('watching');
|
|
121
|
+
const ground = makeGround('watching');
|
|
122
|
+
fs.writeFileSync(path.join(ground, 'a.txt'), 'alpha');
|
|
123
|
+
service.add(ground);
|
|
124
|
+
|
|
125
|
+
const [root] = service.observer.status();
|
|
126
|
+
assert.equal(root.path, ground);
|
|
127
|
+
assert.equal(root.watching, true, 'the doorbell is live');
|
|
128
|
+
assert.equal(root.degraded, false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('the Detect → Record wire is live: a later change reaches the registry', () => {
|
|
132
|
+
const service = makeService('wire');
|
|
133
|
+
const ground = makeGround('wire');
|
|
134
|
+
fs.writeFileSync(path.join(ground, 'a.txt'), 'alpha');
|
|
135
|
+
const { record } = service.add(ground);
|
|
136
|
+
const tree = service.registration.tree(ground);
|
|
137
|
+
|
|
138
|
+
// Ground that appeared after registration. The scan publishes, the
|
|
139
|
+
// boundary records — cut the emit wire in service.js and this fails.
|
|
140
|
+
fs.writeFileSync(path.join(ground, 'later.txt'), 'arrived after');
|
|
141
|
+
service.observer.scanNow(service.observer.listRoots()[0].rootId);
|
|
142
|
+
service.registration.flushIfDirty();
|
|
143
|
+
|
|
144
|
+
const names = tree.registry.children(record.uuid).map((row) => row.name).sort();
|
|
145
|
+
assert.deepEqual(names, ['a.txt', 'later.txt']);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('identity survives a restart over the same directory', () => {
|
|
149
|
+
const dir = path.join(tempRoot, 'state-restart');
|
|
150
|
+
const ground = makeGround('restart');
|
|
151
|
+
fs.writeFileSync(path.join(ground, 'a.txt'), 'alpha');
|
|
152
|
+
|
|
153
|
+
const first = createRegistrationService({ dir });
|
|
154
|
+
const before = first.add(ground).record.uuid;
|
|
155
|
+
first.stop();
|
|
156
|
+
|
|
157
|
+
const second = createRegistrationService({ dir });
|
|
158
|
+
services.push(second);
|
|
159
|
+
const after = second.registration.tree(ground).root().uuid;
|
|
160
|
+
assert.equal(after, before, 'the tree keeps its permanent UUID across processes');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// --- the policies are the real ones ---------------------------------------
|
|
164
|
+
|
|
165
|
+
test("amalgm's enrollment policy is in force, and refuses loudly", () => {
|
|
166
|
+
const service = makeService('policy');
|
|
167
|
+
const excluded = path.join(makeGround('policy'), 'node_modules');
|
|
168
|
+
fs.mkdirSync(excluded);
|
|
169
|
+
assert.throws(() => service.add(excluded), /enrollment policy excludes it/);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('adding ground already inside a registered tree returns that tree', () => {
|
|
173
|
+
const service = makeService('covered');
|
|
174
|
+
const ground = makeGround('covered');
|
|
175
|
+
fs.mkdirSync(path.join(ground, 'inner'));
|
|
176
|
+
fs.writeFileSync(path.join(ground, 'inner', 'a.txt'), 'alpha');
|
|
177
|
+
|
|
178
|
+
const tree = service.add(ground);
|
|
179
|
+
const inner = service.add(path.join(ground, 'inner'));
|
|
180
|
+
assert.equal(inner.rootPath, ground, 'one owner per path — no second tree is born');
|
|
181
|
+
assert.equal(inner.record.uuid, tree.record.uuid);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// --- the endpoint ----------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
function fakeCtx(pathname, method, body) {
|
|
187
|
+
const sent = {};
|
|
188
|
+
return {
|
|
189
|
+
ctx: {
|
|
190
|
+
pathname,
|
|
191
|
+
method,
|
|
192
|
+
readJsonBody: async () => body,
|
|
193
|
+
sendJson: (status, payload) => Object.assign(sent, { status, payload }),
|
|
194
|
+
},
|
|
195
|
+
sent,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
test('POST /entities/add registers and answers with the tree', async () => {
|
|
200
|
+
const ground = makeGround('route');
|
|
201
|
+
fs.writeFileSync(path.join(ground, 'a.txt'), 'alpha');
|
|
202
|
+
|
|
203
|
+
const { ctx, sent } = fakeCtx('/entities/add', 'POST', { path: ground });
|
|
204
|
+
assert.equal(await handleEntityRoutes(ctx), true);
|
|
205
|
+
assert.equal(sent.status, 200);
|
|
206
|
+
assert.equal(sent.payload.rootPath, ground);
|
|
207
|
+
assert.ok(sent.payload.record.uuid);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('the endpoint reports a refusal as a refusal, with the reason', async () => {
|
|
211
|
+
const excluded = path.join(makeGround('route-refused'), '.ssh');
|
|
212
|
+
fs.mkdirSync(excluded);
|
|
213
|
+
|
|
214
|
+
const { ctx, sent } = fakeCtx('/entities/add', 'POST', { path: excluded });
|
|
215
|
+
assert.equal(await handleEntityRoutes(ctx), true);
|
|
216
|
+
assert.equal(sent.status, 400, 'the request named ground the boundary refuses');
|
|
217
|
+
assert.match(sent.payload.error, /enrollment policy excludes it/);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('a missing path is answered, not registered', async () => {
|
|
221
|
+
const { ctx, sent } = fakeCtx('/entities/add', 'POST', {});
|
|
222
|
+
assert.equal(await handleEntityRoutes(ctx), true);
|
|
223
|
+
assert.equal(sent.status, 400);
|
|
224
|
+
assert.match(sent.payload.error, /path is required/);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('ground that is not there is a refusal, never a crash', async () => {
|
|
228
|
+
const { ctx, sent } = fakeCtx('/entities/add', 'POST', { path: path.join(tempRoot, 'no-such-ground') });
|
|
229
|
+
assert.equal(await handleEntityRoutes(ctx), true);
|
|
230
|
+
assert.equal(sent.status, 400);
|
|
231
|
+
assert.ok(sent.payload.error);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('the route is MOUNTED: the runtime dispatcher reaches it', async () => {
|
|
235
|
+
// A handler nothing routes to is a handler that does not exist. This
|
|
236
|
+
// is the local dispatcher production actually serves, unmodified.
|
|
237
|
+
const ground = makeGround('mounted');
|
|
238
|
+
fs.writeFileSync(path.join(ground, 'a.txt'), 'alpha');
|
|
239
|
+
|
|
240
|
+
const { ctx, sent } = fakeCtx('/entities/add', 'POST', { path: ground });
|
|
241
|
+
assert.equal(await createLocalServiceRouter().handle(ctx), true);
|
|
242
|
+
assert.equal(sent.status, 200);
|
|
243
|
+
assert.equal(sent.payload.rootPath, ground);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('the route group claims only its own request', async () => {
|
|
247
|
+
const others = [
|
|
248
|
+
fakeCtx('/entities/add', 'GET', {}),
|
|
249
|
+
fakeCtx('/workspace/projects', 'POST', {}),
|
|
250
|
+
fakeCtx('/entities', 'POST', {}),
|
|
251
|
+
];
|
|
252
|
+
for (const { ctx, sent } of others) {
|
|
253
|
+
assert.equal(await handleEntityRoutes(ctx), false);
|
|
254
|
+
assert.equal(sent.status, undefined, 'an unclaimed request is never answered');
|
|
255
|
+
}
|
|
256
|
+
});
|