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
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The event ledger — replays the detector's published events into a
|
|
5
|
+
* believed entity state and refuses every stream a correct detector could
|
|
6
|
+
* not have produced:
|
|
7
|
+
*
|
|
8
|
+
* - a created fileId or dirId that was ever used before (fresh identity
|
|
9
|
+
* is fresh),
|
|
10
|
+
* - a change whose previousHash breaks the hash chain,
|
|
11
|
+
* - a change to an equal hash (equal versions are silence),
|
|
12
|
+
* - any event on a deleted or unknown channel,
|
|
13
|
+
* - a rename whose `from` is not where the channel lives,
|
|
14
|
+
* - a territory discovered twice, or removed while unknown.
|
|
15
|
+
*
|
|
16
|
+
* Directory channels follow the same grammar minus content: created,
|
|
17
|
+
* renamed, deleted — there is no dir.changed, because a directory has no
|
|
18
|
+
* bytes of its own. Link channels follow the FILE grammar with the written
|
|
19
|
+
* target as the whole payload: the previousTarget chain must hold, and an
|
|
20
|
+
* equal target is silence. `repo.changed` is a raw doorbell, not a state
|
|
21
|
+
* claim, and is ignored — quieting it is the follower's job.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const assert = require('node:assert/strict');
|
|
25
|
+
|
|
26
|
+
function createLedger() {
|
|
27
|
+
const files = new Map(); // fileId -> { rootId, path, hash, deleted }
|
|
28
|
+
const links = new Map(); // linkId -> { rootId, path, target, deleted }
|
|
29
|
+
const dirs = new Map(); // dirId -> { rootId, path, deleted }
|
|
30
|
+
const usedFileIds = new Set();
|
|
31
|
+
const usedLinkIds = new Set();
|
|
32
|
+
const usedDirIds = new Set();
|
|
33
|
+
const territories = new Map(); // absolute path -> rootId
|
|
34
|
+
|
|
35
|
+
function alive(event) {
|
|
36
|
+
const believed = files.get(event.fileId);
|
|
37
|
+
assert.ok(believed, `ledger: ${event.type} for unknown fileId ${event.fileId}`);
|
|
38
|
+
assert.ok(!believed.deleted, `ledger: ${event.type} after deletion of ${event.fileId}`);
|
|
39
|
+
return believed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function aliveLink(event) {
|
|
43
|
+
const believed = links.get(event.linkId);
|
|
44
|
+
assert.ok(believed, `ledger: ${event.type} for unknown linkId ${event.linkId}`);
|
|
45
|
+
assert.ok(!believed.deleted, `ledger: ${event.type} after deletion of ${event.linkId}`);
|
|
46
|
+
return believed;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function aliveDir(event) {
|
|
50
|
+
const believed = dirs.get(event.dirId);
|
|
51
|
+
assert.ok(believed, `ledger: ${event.type} for unknown dirId ${event.dirId}`);
|
|
52
|
+
assert.ok(!believed.deleted, `ledger: ${event.type} after deletion of ${event.dirId}`);
|
|
53
|
+
return believed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
ingest(events) {
|
|
58
|
+
for (const event of events) {
|
|
59
|
+
switch (event.type) {
|
|
60
|
+
case 'file.created': {
|
|
61
|
+
assert.ok(!usedFileIds.has(event.fileId),
|
|
62
|
+
`ledger: created fileId ${event.fileId} was used before — a fresh identity must be fresh`);
|
|
63
|
+
for (const believed of files.values()) {
|
|
64
|
+
if (believed.deleted) continue;
|
|
65
|
+
assert.ok(!(believed.rootId === event.rootId && believed.path === event.path),
|
|
66
|
+
`ledger: two live channels claim ${event.path}`);
|
|
67
|
+
}
|
|
68
|
+
usedFileIds.add(event.fileId);
|
|
69
|
+
files.set(event.fileId, {
|
|
70
|
+
rootId: event.rootId, path: event.path, hash: event.hash, deleted: false,
|
|
71
|
+
});
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'file.changed': {
|
|
75
|
+
const believed = alive(event);
|
|
76
|
+
assert.equal(event.path, believed.path,
|
|
77
|
+
`ledger: change names ${event.path} but the channel lives at ${believed.path}`);
|
|
78
|
+
assert.equal(event.previousHash, believed.hash,
|
|
79
|
+
`ledger: hash chain broken at ${event.path}`);
|
|
80
|
+
assert.notEqual(event.hash, event.previousHash,
|
|
81
|
+
`ledger: equal versions must be silence (${event.path})`);
|
|
82
|
+
believed.hash = event.hash;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'file.renamed': {
|
|
86
|
+
const believed = alive(event);
|
|
87
|
+
assert.equal(event.from, believed.path,
|
|
88
|
+
`ledger: rename from ${event.from} but the channel lives at ${believed.path}`);
|
|
89
|
+
believed.path = event.to;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'file.deleted': {
|
|
93
|
+
const believed = alive(event);
|
|
94
|
+
assert.equal(event.path, believed.path,
|
|
95
|
+
`ledger: delete names ${event.path} but the channel lives at ${believed.path}`);
|
|
96
|
+
believed.deleted = true;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case 'link.created': {
|
|
100
|
+
assert.ok(!usedLinkIds.has(event.linkId),
|
|
101
|
+
`ledger: created linkId ${event.linkId} was used before — a fresh identity must be fresh`);
|
|
102
|
+
for (const believed of links.values()) {
|
|
103
|
+
if (believed.deleted) continue;
|
|
104
|
+
assert.ok(!(believed.rootId === event.rootId && believed.path === event.path),
|
|
105
|
+
`ledger: two live link channels claim ${event.path}`);
|
|
106
|
+
}
|
|
107
|
+
usedLinkIds.add(event.linkId);
|
|
108
|
+
links.set(event.linkId, {
|
|
109
|
+
rootId: event.rootId, path: event.path, target: event.target, deleted: false,
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'link.changed': {
|
|
114
|
+
const believed = aliveLink(event);
|
|
115
|
+
assert.equal(event.path, believed.path,
|
|
116
|
+
`ledger: link change names ${event.path} but the channel lives at ${believed.path}`);
|
|
117
|
+
assert.equal(event.previousTarget, believed.target,
|
|
118
|
+
`ledger: target chain broken at ${event.path}`);
|
|
119
|
+
assert.notEqual(event.target, event.previousTarget,
|
|
120
|
+
`ledger: equal targets must be silence (${event.path})`);
|
|
121
|
+
believed.target = event.target;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'link.renamed': {
|
|
125
|
+
const believed = aliveLink(event);
|
|
126
|
+
assert.equal(event.from, believed.path,
|
|
127
|
+
`ledger: link rename from ${event.from} but the channel lives at ${believed.path}`);
|
|
128
|
+
believed.path = event.to;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
case 'link.deleted': {
|
|
132
|
+
const believed = aliveLink(event);
|
|
133
|
+
assert.equal(event.path, believed.path,
|
|
134
|
+
`ledger: link delete names ${event.path} but the channel lives at ${believed.path}`);
|
|
135
|
+
believed.deleted = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'dir.created': {
|
|
139
|
+
assert.ok(!usedDirIds.has(event.dirId),
|
|
140
|
+
`ledger: created dirId ${event.dirId} was used before — a fresh identity must be fresh`);
|
|
141
|
+
for (const believed of dirs.values()) {
|
|
142
|
+
if (believed.deleted) continue;
|
|
143
|
+
assert.ok(!(believed.rootId === event.rootId && believed.path === event.path),
|
|
144
|
+
`ledger: two live dir channels claim ${event.path}`);
|
|
145
|
+
}
|
|
146
|
+
usedDirIds.add(event.dirId);
|
|
147
|
+
dirs.set(event.dirId, { rootId: event.rootId, path: event.path, deleted: false });
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
case 'dir.renamed': {
|
|
151
|
+
const believed = aliveDir(event);
|
|
152
|
+
assert.equal(event.from, believed.path,
|
|
153
|
+
`ledger: dir rename from ${event.from} but the channel lives at ${believed.path}`);
|
|
154
|
+
believed.path = event.to;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'dir.deleted': {
|
|
158
|
+
const believed = aliveDir(event);
|
|
159
|
+
assert.equal(event.path, believed.path,
|
|
160
|
+
`ledger: dir delete names ${event.path} but the channel lives at ${believed.path}`);
|
|
161
|
+
believed.deleted = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case 'repo.discovered': {
|
|
165
|
+
assert.ok(!territories.has(event.path),
|
|
166
|
+
`ledger: territory ${event.path} discovered twice without removal`);
|
|
167
|
+
territories.set(event.path, event.rootId);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'repo.removed': {
|
|
171
|
+
assert.ok(territories.has(event.path),
|
|
172
|
+
`ledger: territory ${event.path} removed while unknown`);
|
|
173
|
+
territories.delete(event.path);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'repo.changed':
|
|
177
|
+
break; // raw doorbell, never a state claim
|
|
178
|
+
default:
|
|
179
|
+
assert.fail(`ledger: unknown event type ${event.type}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/** Live believed files, optionally for one root: path -> { fileId, hash }. */
|
|
185
|
+
liveFiles(rootId) {
|
|
186
|
+
const live = new Map();
|
|
187
|
+
for (const [fileId, believed] of files) {
|
|
188
|
+
if (believed.deleted) continue;
|
|
189
|
+
if (rootId && believed.rootId !== rootId) continue;
|
|
190
|
+
live.set(believed.path, { fileId, hash: believed.hash });
|
|
191
|
+
}
|
|
192
|
+
return live;
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/** Live believed links, optionally for one root: path -> { linkId, target }. */
|
|
196
|
+
liveLinks(rootId) {
|
|
197
|
+
const live = new Map();
|
|
198
|
+
for (const [linkId, believed] of links) {
|
|
199
|
+
if (believed.deleted) continue;
|
|
200
|
+
if (rootId && believed.rootId !== rootId) continue;
|
|
201
|
+
live.set(believed.path, { linkId, target: believed.target });
|
|
202
|
+
}
|
|
203
|
+
return live;
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/** Live believed directories, optionally for one root: path -> { dirId }. */
|
|
207
|
+
liveDirs(rootId) {
|
|
208
|
+
const live = new Map();
|
|
209
|
+
for (const [dirId, believed] of dirs) {
|
|
210
|
+
if (believed.deleted) continue;
|
|
211
|
+
if (rootId && believed.rootId !== rootId) continue;
|
|
212
|
+
live.set(believed.path, { dirId });
|
|
213
|
+
}
|
|
214
|
+
return live;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
liveTerritories() {
|
|
218
|
+
return new Map(territories);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/** Channel-kind blind: a closed channel is closed, file, link, or dir. */
|
|
222
|
+
isDeleted(channelId) {
|
|
223
|
+
return files.get(channelId)?.deleted === true
|
|
224
|
+
|| links.get(channelId)?.deleted === true
|
|
225
|
+
|| dirs.get(channelId)?.deleted === true;
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
knows(fileId) {
|
|
229
|
+
return files.has(fileId);
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = { createLedger };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The independent disk referee — the oracle's second truth.
|
|
5
|
+
*
|
|
6
|
+
* A deliberately simple test-only reader: plain recursive readdir, plain
|
|
7
|
+
* whole-buffer sha256, its own .git-boundary and policy handling. It shares
|
|
8
|
+
* no traversal or comparison code with the detector (walkRoot,
|
|
9
|
+
* censusBoundaries, diffRootState are all forbidden imports — enforced by a
|
|
10
|
+
* meta-test), so the detector and its referee cannot host the same bug.
|
|
11
|
+
*
|
|
12
|
+
* Read-only by contract: nothing here writes, touches, or scans.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
function hashBytes(absPath) {
|
|
20
|
+
return crypto.createHash('sha256').update(fs.readFileSync(absPath)).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Ground truth for one enrolled root: every enrollable regular file outside
|
|
25
|
+
* repo territory, hashed, every enrollable directory outside repo territory
|
|
26
|
+
* (a directory is ground with no bytes — existence is its whole claim),
|
|
27
|
+
* every enrollable symlink as pointer ground — its target exactly as
|
|
28
|
+
* written, never followed — plus the minimal repo boundaries the walk
|
|
29
|
+
* stops at ('' when the root itself is one). Policy prunes at
|
|
30
|
+
* directories, exactly as the enrollment contract states.
|
|
31
|
+
*/
|
|
32
|
+
function readGround(rootPath, shouldEnroll) {
|
|
33
|
+
const files = new Map(); // relPath -> hash
|
|
34
|
+
const links = new Map(); // relPath -> target, exactly as written
|
|
35
|
+
const dirs = new Set(); // relDir, outside repo territory ('' never listed)
|
|
36
|
+
const repoDirs = [];
|
|
37
|
+
|
|
38
|
+
(function walk(relDir) {
|
|
39
|
+
const absDir = relDir ? path.join(rootPath, relDir) : rootPath;
|
|
40
|
+
let entries;
|
|
41
|
+
try {
|
|
42
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
43
|
+
} catch {
|
|
44
|
+
return; // vanished mid-read; the settled truth is the next read's
|
|
45
|
+
}
|
|
46
|
+
if (entries.some((entry) => entry.name === '.git')) {
|
|
47
|
+
repoDirs.push(relDir);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (relDir) dirs.add(relDir);
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
53
|
+
if (!shouldEnroll(relPath)) continue;
|
|
54
|
+
if (entry.isSymbolicLink()) {
|
|
55
|
+
try {
|
|
56
|
+
links.set(relPath, fs.readlinkSync(path.join(rootPath, relPath)));
|
|
57
|
+
} catch {
|
|
58
|
+
/* vanished mid-read */
|
|
59
|
+
}
|
|
60
|
+
} else if (entry.isDirectory()) walk(relPath);
|
|
61
|
+
else if (entry.isFile()) {
|
|
62
|
+
try {
|
|
63
|
+
files.set(relPath, hashBytes(path.join(rootPath, relPath)));
|
|
64
|
+
} catch {
|
|
65
|
+
/* vanished mid-read */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})('');
|
|
70
|
+
|
|
71
|
+
return { files, links, dirs, repoDirs };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Every repo boundary under rootPath at any depth, as absolute paths —
|
|
76
|
+
* recursing INTO each boundary the way each root runs its own census, with
|
|
77
|
+
* the enrollment policy re-applied relative to that boundary.
|
|
78
|
+
*/
|
|
79
|
+
function allBoundaries(rootPath, shouldEnroll) {
|
|
80
|
+
const found = [];
|
|
81
|
+
|
|
82
|
+
function census(baseAbs) {
|
|
83
|
+
const pending = [''];
|
|
84
|
+
while (pending.length > 0) {
|
|
85
|
+
const relDir = pending.pop();
|
|
86
|
+
const absDir = relDir ? path.join(baseAbs, relDir) : baseAbs;
|
|
87
|
+
let entries;
|
|
88
|
+
try {
|
|
89
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
90
|
+
} catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (relDir && entries.some((entry) => entry.name === '.git')) {
|
|
94
|
+
found.push(absDir);
|
|
95
|
+
census(absDir); // a child's children are the child's own census
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (!entry.isDirectory() || entry.name === '.git') continue;
|
|
100
|
+
const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
101
|
+
if (!shouldEnroll(rel)) continue;
|
|
102
|
+
pending.push(rel);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
census(rootPath);
|
|
108
|
+
let hasRootGit = false;
|
|
109
|
+
try {
|
|
110
|
+
hasRootGit = fs.existsSync(path.join(rootPath, '.git'));
|
|
111
|
+
} catch {
|
|
112
|
+
/* unreadable root: no boundary claim */
|
|
113
|
+
}
|
|
114
|
+
if (hasRootGit) found.push(rootPath);
|
|
115
|
+
return found.sort();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { allBoundaries, hashBytes, readGround };
|