wendkeep 0.85.0 → 0.86.0
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/CHANGELOG.md +40 -0
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/docs/en/commands/evidence-embeddings.md +243 -0
- package/docs/en/commands/mcp.md +67 -7
- package/docs/pt-BR/commands/evidence-embeddings.md +244 -0
- package/docs/pt-BR/commands/mcp.md +66 -7
- package/hooks/evidence-context.mjs +41 -7
- package/hooks/evidence-recall.mjs +10 -0
- package/package.json +1 -1
- package/packages/mcp/src/effects.mjs +3 -2
- package/packages/mcp/src/evidence-recall.mjs +130 -0
- package/packages/mcp/src/executor.mjs +4 -0
- package/packages/mcp/src/server.mjs +31 -1
- package/packages/vault/src/evidence-embedding-plugin.mjs +531 -0
- package/packages/vault/src/evidence-index-store.mjs +360 -0
- package/packages/vault/src/evidence-recall-page.mjs +381 -0
- package/packages/vault/src/evidence-search-index.mjs +917 -0
- package/packages/vault/src/index.mjs +12 -1
- package/packages/vault/src/memory-ledger-view-base.mjs +545 -0
- package/packages/vault/src/memory-ledger-view.mjs +41 -0
- package/packages/vault/src/memory-rotation-store.mjs +967 -0
- package/packages/vault/src/memory-segment-store.mjs +820 -0
- package/packages/vault/src/memory-snapshot-store.mjs +1105 -0
- package/packages/vault/src/memory-store-base.mjs +1161 -0
- package/packages/vault/src/memory-store-core.mjs +2 -0
- package/packages/vault/src/memory-store.mjs +46 -1161
- package/src/doctor.mjs +41 -5
- package/src/evidence-search-health.mjs +221 -0
- package/src/memory-scale-health.mjs +210 -0
- package/src/observer-snapshot.mjs +87 -1
|
@@ -1,1161 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class MemoryOutboxCorruption extends Error {
|
|
50
|
-
constructor(path, cause) {
|
|
51
|
-
super(`Memory outbox file is corrupt: ${path}`);
|
|
52
|
-
this.name = 'MemoryOutboxCorruption';
|
|
53
|
-
this.code = 'MEMORY_OUTBOX_CORRUPT';
|
|
54
|
-
this.path = path;
|
|
55
|
-
this.cause = cause;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function brainDir(vaultBase) {
|
|
60
|
-
return join(vaultBase, '.brain');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function outboxDir(vaultBase) {
|
|
64
|
-
return join(brainDir(vaultBase), 'memory-outbox');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function ledgerPath(vaultBase) {
|
|
68
|
-
return join(brainDir(vaultBase), 'MEMORY_EVENTS.jsonl');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function sharedPath(vaultBase) {
|
|
72
|
-
return join(brainDir(vaultBase), 'SHARED_MEMORY.md');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function corePath(vaultBase) {
|
|
76
|
-
return join(brainDir(vaultBase), 'CORE.md');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function candidatesPath(vaultBase) {
|
|
80
|
-
return join(brainDir(vaultBase), 'MEMORY_CANDIDATES.jsonl');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function lockTarget(vaultBase) {
|
|
84
|
-
return join(brainDir(vaultBase), 'MEMORY');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function unsafeMemoryPath(message) {
|
|
88
|
-
const error = new Error(message);
|
|
89
|
-
error.code = 'VAULT_PATH_UNSAFE';
|
|
90
|
-
return error;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function checkedMemoryFile(vaultBase, path, label, {
|
|
94
|
-
allowMissing = true,
|
|
95
|
-
mustNotExist = false,
|
|
96
|
-
} = {}) {
|
|
97
|
-
return assertVaultPathSafe(vaultBase, path, {
|
|
98
|
-
allowMissing,
|
|
99
|
-
expectedType: 'file',
|
|
100
|
-
mustNotExist,
|
|
101
|
-
label,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function checkedMemoryDirectory(vaultBase, path, label, { allowMissing = true } = {}) {
|
|
106
|
-
return assertVaultPathSafe(vaultBase, path, {
|
|
107
|
-
allowMissing,
|
|
108
|
-
expectedType: 'directory',
|
|
109
|
-
label,
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function readCheckedMemoryFile(vaultBase, path, encoding, label, { allowMissing = false } = {}) {
|
|
114
|
-
let checked = checkedMemoryFile(vaultBase, path, label, { allowMissing });
|
|
115
|
-
if (!checked.exists) return null;
|
|
116
|
-
// Deliberately adjacent to readFileSync: aliases created since the first policy check
|
|
117
|
-
// are rejected before Node opens the artifact.
|
|
118
|
-
checked = checkedMemoryFile(vaultBase, checked.target, label, { allowMissing: false });
|
|
119
|
-
return readFileSync(checked.target, encoding);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function memoryFileIdentityMatches(descriptor, target, {
|
|
123
|
-
platform = process.platform,
|
|
124
|
-
} = {}) {
|
|
125
|
-
if (descriptor.ino !== target.ino) return false;
|
|
126
|
-
// libuv before 1.51 can report an inconsistent Windows volume serial number
|
|
127
|
-
// between stat(path) and fstat(fd). The inode is still the file index; path
|
|
128
|
-
// containment/reparse checks and nlink validation remain independent guards.
|
|
129
|
-
return platform === 'win32' || descriptor.dev === target.dev;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function assertOpenedMemoryFile(vaultBase, path, fd, label) {
|
|
133
|
-
const checked = checkedMemoryFile(vaultBase, path, label, { allowMissing: false });
|
|
134
|
-
// Windows file identities can exceed Number's safe integer range. Node 22.13 may
|
|
135
|
-
// round stat(path) and fstat(fd) differently for the same file, so compare the
|
|
136
|
-
// exact bigint values and keep nlink as the independent hardlink guard.
|
|
137
|
-
const descriptor = fstatSync(fd, { bigint: true });
|
|
138
|
-
const target = statSync(checked.target, { bigint: true });
|
|
139
|
-
if (!descriptor.isFile() || descriptor.nlink > 1n || target.nlink > 1n
|
|
140
|
-
|| !memoryFileIdentityMatches(descriptor, target)) {
|
|
141
|
-
throw unsafeMemoryPath(`${label} mudou de inode ou possui hardlink antes da mutação: ${checked.target}`);
|
|
142
|
-
}
|
|
143
|
-
return checked.target;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function preflightProjectionTargets(vaultBase) {
|
|
147
|
-
return assertVaultPathsSafe(vaultBase, [
|
|
148
|
-
{ path: sharedPath(vaultBase), expectedType: 'file', label: 'projeção SHARED_MEMORY.md' },
|
|
149
|
-
{ path: candidatesPath(vaultBase), expectedType: 'file', label: 'projeção MEMORY_CANDIDATES.jsonl' },
|
|
150
|
-
]);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function canonicalize(value) {
|
|
154
|
-
if (Array.isArray(value)) return value.map(canonicalize);
|
|
155
|
-
if (value && typeof value === 'object') {
|
|
156
|
-
const out = {};
|
|
157
|
-
for (const key of Object.keys(value).sort()) {
|
|
158
|
-
if (value[key] !== undefined) out[key] = canonicalize(value[key]);
|
|
159
|
-
}
|
|
160
|
-
return out;
|
|
161
|
-
}
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function canonicalMemoryJson(value) {
|
|
166
|
-
return JSON.stringify(canonicalize(value));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function sha256(value) {
|
|
170
|
-
return createHash('sha256').update(String(value)).digest('hex');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function hashMemoryValue(value) {
|
|
174
|
-
return sha256(canonicalMemoryJson(value));
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function sanitizeValue(value) {
|
|
178
|
-
if (typeof value === 'string') return sanitizeMemoryText(value);
|
|
179
|
-
if (Array.isArray(value)) return value.map(sanitizeValue);
|
|
180
|
-
if (value && typeof value === 'object') {
|
|
181
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, sanitizeValue(item)]));
|
|
182
|
-
}
|
|
183
|
-
return value;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function sanitizeEvent(event) {
|
|
187
|
-
return sanitizeValue(structuredClone(event));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function projectIdForVault(vaultBase) {
|
|
191
|
-
const path = join(brainDir(vaultBase), 'PROJECT.json');
|
|
192
|
-
const raw = readCheckedMemoryFile(vaultBase, path, 'utf8', 'autoridade PROJECT.json', {
|
|
193
|
-
allowMissing: true,
|
|
194
|
-
});
|
|
195
|
-
if (raw === null) return undefined;
|
|
196
|
-
try {
|
|
197
|
-
const project = JSON.parse(raw);
|
|
198
|
-
return typeof project.projectId === 'string' && project.projectId ? project.projectId : undefined;
|
|
199
|
-
} catch {
|
|
200
|
-
return undefined;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function assertValidEvent(event, vaultBase) {
|
|
205
|
-
const sanitized = sanitizeEvent(event);
|
|
206
|
-
const projectId = vaultBase ? projectIdForVault(vaultBase) : undefined;
|
|
207
|
-
const result = validateMemoryEvent(sanitized, projectId ? { projectId } : {});
|
|
208
|
-
if (!result.ok) {
|
|
209
|
-
const error = new TypeError(`Invalid memory event: ${result.errors.join(' ')}`);
|
|
210
|
-
error.code = 'MEMORY_EVENT_INVALID';
|
|
211
|
-
error.errors = result.errors;
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
if (!EVENT_ID.test(sanitized.event_id)) {
|
|
215
|
-
const error = new TypeError('Invalid memory event: event_id is not filename-safe.');
|
|
216
|
-
error.code = 'MEMORY_EVENT_INVALID';
|
|
217
|
-
throw error;
|
|
218
|
-
}
|
|
219
|
-
return sanitized;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function eventHash(event) {
|
|
223
|
-
return sha256(canonicalMemoryJson(event));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const OUTBOX_PUBLICATION_WAIT_MS = 500;
|
|
227
|
-
const OUTBOX_PUBLICATION_POLL_MS = 5;
|
|
228
|
-
const OUTBOX_PUBLICATION_SIGNAL = new Int32Array(new SharedArrayBuffer(4));
|
|
229
|
-
|
|
230
|
-
function readConcurrentOutboxEvent(vaultBase, path, eventId) {
|
|
231
|
-
const deadline = Date.now() + OUTBOX_PUBLICATION_WAIT_MS;
|
|
232
|
-
while (true) {
|
|
233
|
-
let raw;
|
|
234
|
-
try {
|
|
235
|
-
raw = readCheckedMemoryFile(
|
|
236
|
-
vaultBase, path, 'utf8', 'evento imutável preexistente do outbox',
|
|
237
|
-
);
|
|
238
|
-
} catch (cause) {
|
|
239
|
-
if (cause?.code === 'VAULT_PATH_UNSAFE') throw cause;
|
|
240
|
-
throw new MemoryEventCollision(eventId, `Existing outbox event is unreadable: ${eventId}`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
return JSON.parse(raw);
|
|
245
|
-
} catch (cause) {
|
|
246
|
-
if (!(cause instanceof SyntaxError) || Date.now() >= deadline) {
|
|
247
|
-
throw new MemoryEventCollision(eventId, `Existing outbox event is unreadable: ${eventId}`);
|
|
248
|
-
}
|
|
249
|
-
Atomics.wait(OUTBOX_PUBLICATION_SIGNAL, 0, 0, OUTBOX_PUBLICATION_POLL_MS);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Persist one immutable producer event using exclusive creation. A retry with the same
|
|
256
|
-
* canonical payload is a no-op; reusing the ID for different bytes is an observable error.
|
|
257
|
-
*/
|
|
258
|
-
export function enqueueMemoryEvent(vaultBase, event) {
|
|
259
|
-
const checked = assertValidEvent(event, vaultBase);
|
|
260
|
-
const dir = outboxDir(vaultBase);
|
|
261
|
-
mkdirVaultPath(vaultBase, dir, { label: 'outbox de memória' });
|
|
262
|
-
const path = join(dir, `${checked.event_id}.json`);
|
|
263
|
-
const payload = `${canonicalMemoryJson(checked)}\n`;
|
|
264
|
-
const hash = eventHash(checked);
|
|
265
|
-
|
|
266
|
-
let fd;
|
|
267
|
-
try {
|
|
268
|
-
checkedMemoryFile(vaultBase, path, 'evento imutável do outbox');
|
|
269
|
-
// The final lstat is deliberately adjacent to the exclusive open. A pre-created
|
|
270
|
-
// symlink/hardlink loses the wx race and is revalidated in the EEXIST branch.
|
|
271
|
-
checkedMemoryFile(vaultBase, path, 'evento imutável do outbox');
|
|
272
|
-
fd = openSync(path, 'wx');
|
|
273
|
-
assertOpenedMemoryFile(vaultBase, path, fd, 'evento imutável do outbox');
|
|
274
|
-
writeFileSync(fd, payload, 'utf8');
|
|
275
|
-
fsyncSync(fd);
|
|
276
|
-
assertOpenedMemoryFile(vaultBase, path, fd, 'evento imutável do outbox');
|
|
277
|
-
return { status: 'enqueued', path, eventId: checked.event_id, hash };
|
|
278
|
-
} catch (error) {
|
|
279
|
-
if (error?.code !== 'EEXIST') throw error;
|
|
280
|
-
const existing = readConcurrentOutboxEvent(vaultBase, path, checked.event_id);
|
|
281
|
-
if (eventHash(existing) !== hash || canonicalMemoryJson(existing) !== canonicalMemoryJson(checked)) {
|
|
282
|
-
throw new MemoryEventCollision(checked.event_id);
|
|
283
|
-
}
|
|
284
|
-
return { status: 'duplicate', path, eventId: checked.event_id, hash };
|
|
285
|
-
} finally {
|
|
286
|
-
if (fd !== undefined) closeSync(fd);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function ledgerError(line, message, partial = false) {
|
|
291
|
-
return { line, message, partial };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/** Read a ledger without hiding a valid prefix when its tail is corrupt or partial. */
|
|
295
|
-
export function readMemoryLedger(vaultBase) {
|
|
296
|
-
const path = ledgerPath(vaultBase);
|
|
297
|
-
const checked = checkedMemoryFile(vaultBase, path, 'ledger MEMORY_EVENTS.jsonl');
|
|
298
|
-
if (!checked.exists) {
|
|
299
|
-
return { status: 'ok', path, raw: '', events: [], eventIds: new Set(), errors: [] };
|
|
300
|
-
}
|
|
301
|
-
const raw = readCheckedMemoryFile(
|
|
302
|
-
vaultBase, checked.target, 'utf8', 'ledger MEMORY_EVENTS.jsonl',
|
|
303
|
-
).replace(/\r\n/g, '\n');
|
|
304
|
-
const lines = raw.split('\n');
|
|
305
|
-
const hasPartialTail = raw.length > 0 && !raw.endsWith('\n');
|
|
306
|
-
if (!hasPartialTail) lines.pop();
|
|
307
|
-
const events = [];
|
|
308
|
-
const eventIds = new Set();
|
|
309
|
-
const eventPayloads = new Map();
|
|
310
|
-
const errors = [];
|
|
311
|
-
const projectId = projectIdForVault(vaultBase);
|
|
312
|
-
|
|
313
|
-
lines.forEach((line, index) => {
|
|
314
|
-
const lineNumber = index + 1;
|
|
315
|
-
const partial = hasPartialTail && index === lines.length - 1;
|
|
316
|
-
if (!line.trim()) {
|
|
317
|
-
errors.push(ledgerError(lineNumber, 'blank ledger line', partial));
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
let parsed;
|
|
321
|
-
try {
|
|
322
|
-
parsed = JSON.parse(line);
|
|
323
|
-
} catch (error) {
|
|
324
|
-
errors.push(ledgerError(lineNumber, `invalid JSON: ${error.message}`, partial));
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
const validation = validateMemoryEvent(parsed, projectId ? { projectId } : {});
|
|
328
|
-
if (!validation.ok) {
|
|
329
|
-
errors.push(ledgerError(lineNumber, validation.errors.join(' '), partial));
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
const payload = canonicalMemoryJson(parsed);
|
|
333
|
-
if (eventIds.has(parsed.event_id)) {
|
|
334
|
-
if (eventPayloads.get(parsed.event_id) !== payload) {
|
|
335
|
-
errors.push(ledgerError(lineNumber, `event_id collision: ${parsed.event_id}`, partial));
|
|
336
|
-
} else {
|
|
337
|
-
errors.push(ledgerError(lineNumber, `duplicate event_id: ${parsed.event_id}`, partial));
|
|
338
|
-
}
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
eventIds.add(parsed.event_id);
|
|
342
|
-
eventPayloads.set(parsed.event_id, payload);
|
|
343
|
-
events.push(parsed);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
status: errors.length ? 'corrupt' : 'ok',
|
|
348
|
-
path,
|
|
349
|
-
raw,
|
|
350
|
-
events,
|
|
351
|
-
eventIds,
|
|
352
|
-
errors,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function eventOrder(left, right) {
|
|
357
|
-
return (Number(left.base_revision ?? 0) - Number(right.base_revision ?? 0))
|
|
358
|
-
|| String(left.effective_at || left.observed_at).localeCompare(String(right.effective_at || right.observed_at))
|
|
359
|
-
|| Number(left.turn_sequence ?? 0) - Number(right.turn_sequence ?? 0)
|
|
360
|
-
|| String(left.event_id).localeCompare(String(right.event_id));
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function sameCausalActivation(left, right) {
|
|
364
|
-
return Boolean(left?.canonical_session_id)
|
|
365
|
-
&& left.canonical_session_id === right?.canonical_session_id
|
|
366
|
-
&& left.activation_id === right?.activation_id;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function hasCompleteCausalIdentity(event) {
|
|
370
|
-
return Boolean(event?.canonical_session_id)
|
|
371
|
-
&& Boolean(event?.activation_id)
|
|
372
|
-
&& Boolean(event?.source_turn_id)
|
|
373
|
-
&& Number.isInteger(event?.activation_epoch)
|
|
374
|
-
&& Number.isInteger(event?.turn_sequence);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function sameCompleteCausalLineage(left, right) {
|
|
378
|
-
return hasCompleteCausalIdentity(left)
|
|
379
|
-
&& hasCompleteCausalIdentity(right)
|
|
380
|
-
&& left.canonical_session_id === right.canonical_session_id
|
|
381
|
-
&& left.activation_id === right.activation_id
|
|
382
|
-
&& left.activation_epoch === right.activation_epoch;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function comparable(left, right) {
|
|
386
|
-
if (left?.project_id !== right?.project_id || !sameMemoryScope(left, right)) return false;
|
|
387
|
-
if (sameCausalActivation(left, right)) return true;
|
|
388
|
-
const leftSupersedes = left.supersedes_event_id || left.supersedes;
|
|
389
|
-
const rightSupersedes = right.supersedes_event_id || right.supersedes;
|
|
390
|
-
return leftSupersedes === right.event_id || rightSupersedes === left.event_id
|
|
391
|
-
|| (Array.isArray(leftSupersedes) && leftSupersedes.includes(right.event_id))
|
|
392
|
-
|| (Array.isArray(rightSupersedes) && rightSupersedes.includes(left.event_id));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function conflictGroupKey(event) {
|
|
396
|
-
if (event.operation !== 'replace') return null;
|
|
397
|
-
if (!Number.isInteger(event.base_revision) || typeof event.base_value_hash !== 'string') return null;
|
|
398
|
-
return `${memoryRecordKey(event)}\u0000${event.base_revision}\u0000${event.base_value_hash}`;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function candidateId(reason, memoryKey, eventIds) {
|
|
402
|
-
return `memcand-${sha256(`${reason}\u0000${memoryKey}\u0000${eventIds.join('\u0000')}`).slice(0, 16)}`;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// CORE is prose, not an operational data store. Only an explicit, single-line marker
|
|
406
|
-
// participates in precedence so the projector never guesses meaning from human text:
|
|
407
|
-
// <!-- wk-memory: release.push="manual-only" -->
|
|
408
|
-
function readCoreInvariants(vaultBase) {
|
|
409
|
-
const core = readCheckedMemoryFile(
|
|
410
|
-
vaultBase, corePath(vaultBase), 'utf8', 'autoridade CORE.md', { allowMissing: true },
|
|
411
|
-
);
|
|
412
|
-
if (core === null) return new Map();
|
|
413
|
-
const invariants = new Map();
|
|
414
|
-
const marker = /^<!--\s*wk-memory:\s*([A-Za-z0-9][A-Za-z0-9._-]*)=(.+)\s*-->$/;
|
|
415
|
-
for (const line of core.split('\n')) {
|
|
416
|
-
const match = line.trim().match(marker);
|
|
417
|
-
if (!match) continue;
|
|
418
|
-
try {
|
|
419
|
-
invariants.set(match[1], JSON.parse(match[2].trim()));
|
|
420
|
-
} catch { /* malformed prose markers do not become implicit authority */ }
|
|
421
|
-
}
|
|
422
|
-
return invariants;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function blockedByCoreCandidate(event, coreValue) {
|
|
426
|
-
return {
|
|
427
|
-
v: 1,
|
|
428
|
-
candidate_id: candidateId('blocked_by_core', event.memory_key, [event.event_id]),
|
|
429
|
-
reason: 'blocked_by_core',
|
|
430
|
-
status: 'blocked_by_core',
|
|
431
|
-
memory_key: event.memory_key,
|
|
432
|
-
...(event.scope ? { scope: effectiveMemoryScope(event), record_key: memoryRecordKey(event) } : {}),
|
|
433
|
-
event_ids: [event.event_id],
|
|
434
|
-
proposed_value: event.value,
|
|
435
|
-
core_value: coreValue,
|
|
436
|
-
provenance: {
|
|
437
|
-
authority: 'core',
|
|
438
|
-
source: '.brain/CORE.md',
|
|
439
|
-
core_value_hash: hashMemoryValue(coreValue),
|
|
440
|
-
},
|
|
441
|
-
events: [event],
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function conflictCandidate(memoryKey, events, currentEvent = null) {
|
|
446
|
-
const ordered = [...events].sort(eventOrder);
|
|
447
|
-
const eventIds = ordered.map((item) => item.event_id).sort();
|
|
448
|
-
const byId = new Map(ordered.map((item) => [item.event_id, item]));
|
|
449
|
-
return {
|
|
450
|
-
v: 1,
|
|
451
|
-
candidate_id: candidateId('conflict', memoryKey, eventIds),
|
|
452
|
-
reason: 'conflict',
|
|
453
|
-
memory_key: memoryKey,
|
|
454
|
-
...((ordered[0] || currentEvent)?.scope ? {
|
|
455
|
-
scope: effectiveMemoryScope(ordered[0] || currentEvent || {}),
|
|
456
|
-
record_key: memoryRecordKey(ordered[0] || currentEvent || { memory_key: memoryKey }),
|
|
457
|
-
} : {}),
|
|
458
|
-
event_ids: eventIds,
|
|
459
|
-
values: eventIds.map((id) => byId.get(id).value),
|
|
460
|
-
base_revision: ordered[0]?.base_revision ?? currentEvent?.revision ?? 0,
|
|
461
|
-
base_value_hash: ordered[0]?.base_value_hash ?? (currentEvent ? hashMemoryValue(currentEvent.value) : null),
|
|
462
|
-
events: eventIds.map((id) => byId.get(id)),
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function conflictReviewEvent(candidate, candidateCount = 1) {
|
|
467
|
-
const source = candidate.events?.[0] || {};
|
|
468
|
-
const memoryKey = sanitizeMemoryText(candidate.memory_key || 'unknown');
|
|
469
|
-
const eventCount = Array.isArray(candidate.event_ids) ? candidate.event_ids.length : 0;
|
|
470
|
-
return {
|
|
471
|
-
v: 1,
|
|
472
|
-
event_id: `mem-review-${candidate.candidate_id}`,
|
|
473
|
-
project_id: source.project_id || '',
|
|
474
|
-
memory_key: candidate.memory_key,
|
|
475
|
-
scope: candidate.scope,
|
|
476
|
-
operation: 'assert',
|
|
477
|
-
value: `[revisão pendente: ${memoryKey}; candidates: ${candidateCount}; events: ${eventCount}]`,
|
|
478
|
-
authority: 'candidate',
|
|
479
|
-
canonical_session_id: source.canonical_session_id || 'memory-reducer',
|
|
480
|
-
activation_id: source.activation_id || 'memory-reducer',
|
|
481
|
-
activation_epoch: Number.isInteger(source.activation_epoch) ? source.activation_epoch : 0,
|
|
482
|
-
turn_sequence: 0,
|
|
483
|
-
source_turn_id: 'memory-review',
|
|
484
|
-
observed_at: source.observed_at || new Date(0).toISOString(),
|
|
485
|
-
evidence: ['MEMORY_CANDIDATES.jsonl'],
|
|
486
|
-
review_pending: true,
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function sortedObject(entries) {
|
|
491
|
-
return Object.fromEntries([...entries].sort(([left], [right]) => left.localeCompare(right)));
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
function currentEventFromRecord(record) {
|
|
495
|
-
if (!record) return null;
|
|
496
|
-
return { ...record.source, value: record.value, revision: record.revision };
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function supersededTransitively(superseded, sourceEventId, finalEventId) {
|
|
500
|
-
if (!sourceEventId || !finalEventId || sourceEventId === finalEventId) return false;
|
|
501
|
-
const edges = new Map();
|
|
502
|
-
for (const item of superseded) {
|
|
503
|
-
if (!edges.has(item.event_id)) edges.set(item.event_id, []);
|
|
504
|
-
edges.get(item.event_id).push(item.by_event_id);
|
|
505
|
-
}
|
|
506
|
-
const pending = [sourceEventId];
|
|
507
|
-
const visited = new Set();
|
|
508
|
-
while (pending.length) {
|
|
509
|
-
const current = pending.shift();
|
|
510
|
-
if (visited.has(current)) continue;
|
|
511
|
-
visited.add(current);
|
|
512
|
-
for (const next of edges.get(current) || []) {
|
|
513
|
-
if (next === finalEventId) return true;
|
|
514
|
-
if (!visited.has(next)) pending.push(next);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
return false;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function isCausallyOlder(event, current) {
|
|
521
|
-
if (!current) return false;
|
|
522
|
-
if (sameCausalActivation(event, current)) {
|
|
523
|
-
return Number(event.turn_sequence) < Number(current.turn_sequence);
|
|
524
|
-
}
|
|
525
|
-
if (event.canonical_session_id && event.canonical_session_id === current.canonical_session_id
|
|
526
|
-
&& sameMemoryScope(event, current)
|
|
527
|
-
&& Number.isInteger(event.activation_epoch) && Number.isInteger(current.activation_epoch)
|
|
528
|
-
&& event.activation_epoch !== current.activation_epoch) {
|
|
529
|
-
return event.activation_epoch < current.activation_epoch;
|
|
530
|
-
}
|
|
531
|
-
const eventEffective = Date.parse(event.effective_at || '');
|
|
532
|
-
const currentEffective = Date.parse(current.effective_at || '');
|
|
533
|
-
return Number.isFinite(eventEffective) && Number.isFinite(currentEffective)
|
|
534
|
-
&& eventEffective < currentEffective;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const AUTHORITY_RANK = Object.freeze({ candidate: 0, reported: 1, verified: 2 });
|
|
538
|
-
|
|
539
|
-
function sameRegisterLineage(left, right) {
|
|
540
|
-
return Boolean(left?.canonical_session_id)
|
|
541
|
-
&& left.canonical_session_id === right?.canonical_session_id
|
|
542
|
-
&& left.project_id === right?.project_id
|
|
543
|
-
&& sameMemoryScope(left, right);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/** Positive means `incoming` is a safe successor; null means human comparison is required. */
|
|
547
|
-
function registerPrecedence(incoming, current) {
|
|
548
|
-
if (!incoming?.scope || !current?.scope
|
|
549
|
-
|| !isRegisterMemoryKey(incoming?.memory_key) || !sameRegisterLineage(incoming, current)) return null;
|
|
550
|
-
const epoch = Number(incoming.activation_epoch ?? -1) - Number(current.activation_epoch ?? -1);
|
|
551
|
-
if (epoch) return epoch;
|
|
552
|
-
const turn = Number(incoming.turn_sequence ?? -1) - Number(current.turn_sequence ?? -1);
|
|
553
|
-
if (turn) return turn;
|
|
554
|
-
const authority = (AUTHORITY_RANK[incoming.authority] ?? -1) - (AUTHORITY_RANK[current.authority] ?? -1);
|
|
555
|
-
if (authority) return authority;
|
|
556
|
-
const observed = String(incoming.observed_at || '').localeCompare(String(current.observed_at || ''));
|
|
557
|
-
if (observed) return observed;
|
|
558
|
-
return String(incoming.event_id || '').localeCompare(String(current.event_id || ''));
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Pure deterministic reducer. It pre-detects incomparable scalar siblings so replay order
|
|
563
|
-
* never turns one concurrent writer into an accidental winner.
|
|
564
|
-
*/
|
|
565
|
-
export function reduceMemoryEvents(inputEvents = [], {
|
|
566
|
-
coreInvariants = new Map(), resolveDeferredAsserts = true,
|
|
567
|
-
} = {}) {
|
|
568
|
-
const protectedValues = coreInvariants instanceof Map
|
|
569
|
-
? coreInvariants
|
|
570
|
-
: new Map(Object.entries(coreInvariants || {}));
|
|
571
|
-
const unique = new Map();
|
|
572
|
-
for (const raw of inputEvents) {
|
|
573
|
-
const event = assertValidEvent(raw);
|
|
574
|
-
const existing = unique.get(event.event_id);
|
|
575
|
-
if (existing && canonicalMemoryJson(existing) !== canonicalMemoryJson(event)) {
|
|
576
|
-
throw new MemoryEventCollision(event.event_id, `Ledger contains divergent payloads for ${event.event_id}`);
|
|
577
|
-
}
|
|
578
|
-
if (!existing) unique.set(event.event_id, event);
|
|
579
|
-
}
|
|
580
|
-
const rescopeTargets = new Set(
|
|
581
|
-
[...unique.values()].flatMap((item) => [
|
|
582
|
-
item.rescopes_event_id,
|
|
583
|
-
...(Array.isArray(item.rescopes_event_ids) ? item.rescopes_event_ids : []),
|
|
584
|
-
]).filter(Boolean),
|
|
585
|
-
);
|
|
586
|
-
const projectIds = new Set([...unique.values()].map((item) => item.project_id).filter(Boolean));
|
|
587
|
-
if (projectIds.size > 1) {
|
|
588
|
-
const error = new TypeError('Memory reducer cannot compare events from different projects.');
|
|
589
|
-
error.code = 'MEMORY_PROJECT_MIXED';
|
|
590
|
-
throw error;
|
|
591
|
-
}
|
|
592
|
-
const events = [...unique.values()]
|
|
593
|
-
.filter((item) => !rescopeTargets.has(item.event_id))
|
|
594
|
-
.sort(eventOrder);
|
|
595
|
-
const candidateDecisions = new Map();
|
|
596
|
-
for (const item of events) {
|
|
597
|
-
const decision = item.candidate_decision;
|
|
598
|
-
if (!decision) continue;
|
|
599
|
-
const existing = candidateDecisions.get(decision.candidate_id);
|
|
600
|
-
if (existing && canonicalMemoryJson(existing.decision) !== canonicalMemoryJson(decision)) {
|
|
601
|
-
throw new MemoryEventCollision(
|
|
602
|
-
decision.candidate_id,
|
|
603
|
-
`Ledger contains incompatible decisions for candidate ${decision.candidate_id}`,
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
if (!existing) candidateDecisions.set(decision.candidate_id, { decision, event: item });
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const peerGroups = new Map();
|
|
610
|
-
for (const item of events) {
|
|
611
|
-
const key = conflictGroupKey(item);
|
|
612
|
-
if (!key) continue;
|
|
613
|
-
if (!peerGroups.has(key)) peerGroups.set(key, []);
|
|
614
|
-
peerGroups.get(key).push(item);
|
|
615
|
-
}
|
|
616
|
-
const conflictingIds = new Set();
|
|
617
|
-
const groupedCandidates = new Map();
|
|
618
|
-
for (const [key, group] of peerGroups) {
|
|
619
|
-
const incomparable = group.filter((item, index) => group.some((other, otherIndex) => index !== otherIndex && !comparable(item, other)));
|
|
620
|
-
if (incomparable.length < 2) continue;
|
|
621
|
-
const ordered = [...incomparable].sort(eventOrder);
|
|
622
|
-
ordered.forEach((item) => conflictingIds.add(item.event_id));
|
|
623
|
-
groupedCandidates.set(key, ordered);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const records = new Map();
|
|
627
|
-
const tombstones = new Map();
|
|
628
|
-
const candidates = [];
|
|
629
|
-
const pendingAssertConflicts = [];
|
|
630
|
-
const resolvedCandidateIds = new Set();
|
|
631
|
-
const emittedGroups = new Set();
|
|
632
|
-
const appliedEventIds = [];
|
|
633
|
-
const superseded = [];
|
|
634
|
-
let revision = 0;
|
|
635
|
-
|
|
636
|
-
for (const item of events) {
|
|
637
|
-
if (item.candidate_decision && item.candidate_decision.action === 'reject') {
|
|
638
|
-
appliedEventIds.push(item.event_id);
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (protectedValues.has(item.memory_key)) {
|
|
643
|
-
const coreValue = protectedValues.get(item.memory_key);
|
|
644
|
-
const agreesWithCore = item.operation === 'assert'
|
|
645
|
-
&& hashMemoryValue(item.value) === hashMemoryValue(coreValue);
|
|
646
|
-
if (!agreesWithCore) {
|
|
647
|
-
candidates.push(blockedByCoreCandidate(item, coreValue));
|
|
648
|
-
continue;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const groupKey = conflictGroupKey(item);
|
|
653
|
-
if (conflictingIds.has(item.event_id)) {
|
|
654
|
-
if (!emittedGroups.has(groupKey)) {
|
|
655
|
-
candidates.push(conflictCandidate(item.memory_key, groupedCandidates.get(groupKey)));
|
|
656
|
-
emittedGroups.add(groupKey);
|
|
657
|
-
}
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
const recordKey = memoryRecordKey(item);
|
|
662
|
-
const current = records.get(recordKey);
|
|
663
|
-
const currentSource = current?.source;
|
|
664
|
-
if (isCausallyOlder(item, currentSource)) {
|
|
665
|
-
superseded.push({ event_id: item.event_id, by_event_id: currentSource.event_id });
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
if (item.operation === 'assert') {
|
|
670
|
-
if (current && hashMemoryValue(current.value) !== hashMemoryValue(item.value)) {
|
|
671
|
-
const precedence = registerPrecedence(item, currentSource);
|
|
672
|
-
if ((sameCausalActivation(item, currentSource)
|
|
673
|
-
&& Number(item.turn_sequence) > Number(currentSource.turn_sequence))
|
|
674
|
-
|| precedence > 0) {
|
|
675
|
-
records.set(recordKey, { value: item.value, revision: current.revision + 1, source: item });
|
|
676
|
-
tombstones.delete(recordKey);
|
|
677
|
-
superseded.push({ event_id: currentSource.event_id, by_event_id: item.event_id });
|
|
678
|
-
revision += 1;
|
|
679
|
-
appliedEventIds.push(item.event_id);
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
682
|
-
const candidate = conflictCandidate(item.memory_key, [currentEventFromRecord(current), item]);
|
|
683
|
-
candidates.push(candidate);
|
|
684
|
-
pendingAssertConflicts.push({ candidate, event: item });
|
|
685
|
-
continue;
|
|
686
|
-
}
|
|
687
|
-
if (!current) {
|
|
688
|
-
records.set(recordKey, { value: item.value, revision: 1, source: item });
|
|
689
|
-
tombstones.delete(recordKey);
|
|
690
|
-
revision += 1;
|
|
691
|
-
}
|
|
692
|
-
appliedEventIds.push(item.event_id);
|
|
693
|
-
continue;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
if (item.operation === 'add') {
|
|
697
|
-
const oldValues = Array.isArray(current?.value) ? current.value : (current ? [current.value] : []);
|
|
698
|
-
const additions = Array.isArray(item.value) ? item.value : [item.value];
|
|
699
|
-
const byHash = new Map(oldValues.map((value) => [hashMemoryValue(value), value]));
|
|
700
|
-
additions.forEach((value) => byHash.set(hashMemoryValue(value), value));
|
|
701
|
-
const value = [...byHash.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([, entry]) => entry);
|
|
702
|
-
records.set(recordKey, { value, revision: (current?.revision || 0) + 1, source: item });
|
|
703
|
-
tombstones.delete(recordKey);
|
|
704
|
-
revision += 1;
|
|
705
|
-
appliedEventIds.push(item.event_id);
|
|
706
|
-
continue;
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
if (item.operation === 'remove') {
|
|
710
|
-
if (!current) {
|
|
711
|
-
appliedEventIds.push(item.event_id);
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
const baseMatches = item.base_revision === undefined
|
|
715
|
-
|| (item.base_revision === current.revision
|
|
716
|
-
&& (!item.base_value_hash || item.base_value_hash === hashMemoryValue(current.value)));
|
|
717
|
-
if (!baseMatches) {
|
|
718
|
-
candidates.push(conflictCandidate(item.memory_key, [currentEventFromRecord(current), item]));
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
if (item.value !== null && item.value !== undefined && Array.isArray(current.value)) {
|
|
722
|
-
const removalHash = hashMemoryValue(item.value);
|
|
723
|
-
const value = current.value.filter((entry) => hashMemoryValue(entry) !== removalHash);
|
|
724
|
-
records.set(recordKey, { value, revision: current.revision + 1, source: item });
|
|
725
|
-
tombstones.set(`${recordKey}:${removalHash}`, {
|
|
726
|
-
event_id: item.event_id, removed_event_id: current.source.event_id, value_hash: removalHash,
|
|
727
|
-
});
|
|
728
|
-
} else {
|
|
729
|
-
records.delete(recordKey);
|
|
730
|
-
tombstones.set(recordKey, {
|
|
731
|
-
event_id: item.event_id,
|
|
732
|
-
removed_event_id: current.source.event_id,
|
|
733
|
-
value_hash: hashMemoryValue(current.value),
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
revision += 1;
|
|
737
|
-
appliedEventIds.push(item.event_id);
|
|
738
|
-
continue;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// replace
|
|
742
|
-
const explicitlySupersedes = item.supersedes_event_id === currentSource?.event_id
|
|
743
|
-
|| (Array.isArray(item.supersedes) && item.supersedes.includes(currentSource?.event_id));
|
|
744
|
-
const baseMatches = current
|
|
745
|
-
&& item.base_revision === current.revision
|
|
746
|
-
&& item.base_value_hash === hashMemoryValue(current.value);
|
|
747
|
-
if (!current || (!baseMatches && !explicitlySupersedes)) {
|
|
748
|
-
candidates.push(conflictCandidate(item.memory_key, current ? [currentEventFromRecord(current), item] : [item]));
|
|
749
|
-
continue;
|
|
750
|
-
}
|
|
751
|
-
records.set(recordKey, { value: item.value, revision: current.revision + 1, source: item });
|
|
752
|
-
tombstones.delete(recordKey);
|
|
753
|
-
const explicitlySupersededIds = new Set(
|
|
754
|
-
Array.isArray(item.supersedes)
|
|
755
|
-
? item.supersedes
|
|
756
|
-
: (item.supersedes_event_id ? [item.supersedes_event_id] : []),
|
|
757
|
-
);
|
|
758
|
-
if (item.candidate_decision?.action === 'promote' && explicitlySupersededIds.size) {
|
|
759
|
-
for (const candidate of candidates) {
|
|
760
|
-
if (candidate.reason !== 'conflict' || !candidate.event_ids?.length) continue;
|
|
761
|
-
if (candidate.event_ids.every((eventId) => explicitlySupersededIds.has(eventId))) {
|
|
762
|
-
resolvedCandidateIds.add(candidate.candidate_id);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
for (const pending of pendingAssertConflicts) {
|
|
766
|
-
if (explicitlySupersededIds.has(pending.event.event_id)) {
|
|
767
|
-
resolvedCandidateIds.add(pending.candidate.candidate_id);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
revision += 1;
|
|
772
|
-
appliedEventIds.push(item.event_id);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
if (resolveDeferredAsserts) {
|
|
776
|
-
// A physically late assert can sort before a corrective promotion because effective time
|
|
777
|
-
// precedes CLI decision time. Revisit only scalar assert conflicts left without an explicit
|
|
778
|
-
// decision, against the final complete causal source; the ledger and global ordering stay put.
|
|
779
|
-
const deferredAsserts = pendingAssertConflicts
|
|
780
|
-
.filter((pending) => !candidateDecisions.has(pending.candidate.candidate_id))
|
|
781
|
-
.sort((left, right) => String(left.event.memory_key).localeCompare(String(right.event.memory_key))
|
|
782
|
-
|| String(left.event.canonical_session_id || '').localeCompare(String(right.event.canonical_session_id || ''))
|
|
783
|
-
|| String(left.event.activation_id || '').localeCompare(String(right.event.activation_id || ''))
|
|
784
|
-
|| Number(left.event.activation_epoch ?? -1) - Number(right.event.activation_epoch ?? -1)
|
|
785
|
-
|| Number(left.event.turn_sequence ?? -1) - Number(right.event.turn_sequence ?? -1)
|
|
786
|
-
|| eventOrder(left.event, right.event));
|
|
787
|
-
let advanced = true;
|
|
788
|
-
while (advanced) {
|
|
789
|
-
advanced = false;
|
|
790
|
-
for (const pending of deferredAsserts) {
|
|
791
|
-
if (resolvedCandidateIds.has(pending.candidate.candidate_id)) continue;
|
|
792
|
-
const current = records.get(memoryRecordKey(pending.event));
|
|
793
|
-
const currentSource = current?.source;
|
|
794
|
-
if (!sameCompleteCausalLineage(pending.event, currentSource)) continue;
|
|
795
|
-
if (pending.event.turn_sequence > currentSource.turn_sequence) {
|
|
796
|
-
records.set(memoryRecordKey(pending.event), {
|
|
797
|
-
value: pending.event.value,
|
|
798
|
-
revision: current.revision + 1,
|
|
799
|
-
source: pending.event,
|
|
800
|
-
});
|
|
801
|
-
tombstones.delete(memoryRecordKey(pending.event));
|
|
802
|
-
superseded.push({ event_id: currentSource.event_id, by_event_id: pending.event.event_id });
|
|
803
|
-
revision += 1;
|
|
804
|
-
appliedEventIds.push(pending.event.event_id);
|
|
805
|
-
resolvedCandidateIds.add(pending.candidate.candidate_id);
|
|
806
|
-
advanced = true;
|
|
807
|
-
} else if (pending.event.turn_sequence < currentSource.turn_sequence) {
|
|
808
|
-
superseded.push({ event_id: pending.event.event_id, by_event_id: currentSource.event_id });
|
|
809
|
-
resolvedCandidateIds.add(pending.candidate.candidate_id);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const pendingByCandidateId = new Map(
|
|
816
|
-
pendingAssertConflicts.map((pending) => [pending.candidate.candidate_id, pending]),
|
|
817
|
-
);
|
|
818
|
-
const reanchoredCandidates = candidates
|
|
819
|
-
.map((candidate) => {
|
|
820
|
-
const pending = pendingByCandidateId.get(candidate.candidate_id);
|
|
821
|
-
if (!pending) return candidate;
|
|
822
|
-
const finalSource = currentEventFromRecord(records.get(candidate.record_key || candidate.memory_key));
|
|
823
|
-
const previousSource = candidate.events?.find(
|
|
824
|
-
(event) => event.event_id !== pending.event.event_id,
|
|
825
|
-
);
|
|
826
|
-
if (!finalSource || !previousSource || finalSource.event_id === previousSource.event_id) {
|
|
827
|
-
return candidate;
|
|
828
|
-
}
|
|
829
|
-
if (!sameCompleteCausalLineage(previousSource, finalSource)
|
|
830
|
-
|| !supersededTransitively(superseded, previousSource.event_id, finalSource.event_id)) {
|
|
831
|
-
return candidate;
|
|
832
|
-
}
|
|
833
|
-
if (hashMemoryValue(finalSource.value) === hashMemoryValue(pending.event.value)) return null;
|
|
834
|
-
return conflictCandidate(candidate.memory_key, [finalSource, pending.event], finalSource);
|
|
835
|
-
})
|
|
836
|
-
.filter(Boolean);
|
|
837
|
-
|
|
838
|
-
const stateEntries = [...records].map(([key, record]) => [key, record.value]);
|
|
839
|
-
const recordEntries = [...records].map(([key, record]) => [key, record]);
|
|
840
|
-
const tombstoneEntries = [...tombstones];
|
|
841
|
-
const state = sortedObject(stateEntries);
|
|
842
|
-
const recordObject = sortedObject(recordEntries);
|
|
843
|
-
const tombstoneObject = sortedObject(tombstoneEntries);
|
|
844
|
-
const unresolvedCandidates = reanchoredCandidates
|
|
845
|
-
.filter((item) => !candidateDecisions.has(item.candidate_id)
|
|
846
|
-
&& !resolvedCandidateIds.has(item.candidate_id));
|
|
847
|
-
unresolvedCandidates.sort((left, right) => left.candidate_id.localeCompare(right.candidate_id));
|
|
848
|
-
superseded.sort((left, right) => left.event_id.localeCompare(right.event_id));
|
|
849
|
-
const eventCursor = events.at(-1)?.event_id || 'none';
|
|
850
|
-
const stateHash = hashMemoryValue({ state, tombstones: tombstoneObject });
|
|
851
|
-
const ambiguousRecordKeys = new Set(
|
|
852
|
-
unresolvedCandidates.map((candidate) => candidate.record_key || candidate.memory_key),
|
|
853
|
-
);
|
|
854
|
-
const activeEvents = [
|
|
855
|
-
...Object.entries(recordObject)
|
|
856
|
-
.filter(([recordKey]) => !ambiguousRecordKeys.has(recordKey))
|
|
857
|
-
.map(([recordKey, record]) => ({
|
|
858
|
-
...record.source,
|
|
859
|
-
memory_key: record.source.memory_key,
|
|
860
|
-
projection_key: recordKey,
|
|
861
|
-
operation: 'assert',
|
|
862
|
-
value: record.value,
|
|
863
|
-
})),
|
|
864
|
-
...unresolvedCandidates.map((candidate) => (
|
|
865
|
-
conflictReviewEvent(candidate, unresolvedCandidates.length)
|
|
866
|
-
)),
|
|
867
|
-
];
|
|
868
|
-
|
|
869
|
-
return {
|
|
870
|
-
state,
|
|
871
|
-
records: recordObject,
|
|
872
|
-
candidates: unresolvedCandidates,
|
|
873
|
-
tombstones: tombstoneObject,
|
|
874
|
-
superseded,
|
|
875
|
-
appliedEventIds,
|
|
876
|
-
eventIds: events.map((item) => item.event_id),
|
|
877
|
-
activeEvents,
|
|
878
|
-
revision,
|
|
879
|
-
eventCursor,
|
|
880
|
-
stateHash,
|
|
881
|
-
};
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
/**
|
|
885
|
-
* Re-derive one ledger projection using the explicit invariants declared by CORE.
|
|
886
|
-
* `eventCursor` is the reducer's deterministic causal cursor; `ledgerCursor` is the
|
|
887
|
-
* physical prefix boundary used by durable checkpoints.
|
|
888
|
-
*/
|
|
889
|
-
export function deriveMemoryProjection(vaultBase, inputEvents = [], {
|
|
890
|
-
resolveDeferredAsserts = true,
|
|
891
|
-
} = {}) {
|
|
892
|
-
const reduced = reduceMemoryEvents(inputEvents, {
|
|
893
|
-
coreInvariants: readCoreInvariants(vaultBase), resolveDeferredAsserts,
|
|
894
|
-
});
|
|
895
|
-
const ledgerCursor = inputEvents.at(-1)?.event_id || 'none';
|
|
896
|
-
const checkpoint = {
|
|
897
|
-
revision: reduced.revision,
|
|
898
|
-
event_cursor: ledgerCursor,
|
|
899
|
-
state_hash: reduced.stateHash,
|
|
900
|
-
};
|
|
901
|
-
if (reduced.eventCursor !== ledgerCursor) {
|
|
902
|
-
checkpoint.causal_event_cursor = reduced.eventCursor;
|
|
903
|
-
}
|
|
904
|
-
return {
|
|
905
|
-
...reduced,
|
|
906
|
-
ledgerCursor,
|
|
907
|
-
checkpoint,
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
function readOutbox(vaultBase) {
|
|
912
|
-
const dir = outboxDir(vaultBase);
|
|
913
|
-
let checkedDir = checkedMemoryDirectory(vaultBase, dir, 'outbox de memória');
|
|
914
|
-
if (!checkedDir.exists) return [];
|
|
915
|
-
checkedDir = checkedMemoryDirectory(vaultBase, checkedDir.target, 'outbox de memória', {
|
|
916
|
-
allowMissing: false,
|
|
917
|
-
});
|
|
918
|
-
const entries = readdirSync(checkedDir.target, { withFileTypes: true });
|
|
919
|
-
for (const entry of entries) {
|
|
920
|
-
assertVaultPathSafe(vaultBase, join(checkedDir.target, entry.name), {
|
|
921
|
-
allowMissing: false,
|
|
922
|
-
label: `entrada ${entry.name} do outbox de memória`,
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
return entries
|
|
926
|
-
.filter((entry) => entry.name.endsWith('.json'))
|
|
927
|
-
.sort((left, right) => left.name.localeCompare(right.name))
|
|
928
|
-
.map((entry) => {
|
|
929
|
-
const path = join(checkedDir.target, entry.name);
|
|
930
|
-
try {
|
|
931
|
-
const event = assertValidEvent(JSON.parse(readCheckedMemoryFile(
|
|
932
|
-
vaultBase, path, 'utf8', `evento ${entry.name} do outbox de memória`,
|
|
933
|
-
)), vaultBase);
|
|
934
|
-
if (`${event.event_id}.json` !== entry.name) throw new Error('filename does not match event_id');
|
|
935
|
-
return { event, path };
|
|
936
|
-
} catch (error) {
|
|
937
|
-
if (error?.code === 'VAULT_PATH_UNSAFE') throw error;
|
|
938
|
-
throw new MemoryOutboxCorruption(path, error);
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function countPendingOutbox(vaultBase) {
|
|
944
|
-
const dir = outboxDir(vaultBase);
|
|
945
|
-
let checked = checkedMemoryDirectory(vaultBase, dir, 'outbox de memória');
|
|
946
|
-
if (!checked.exists) return 0;
|
|
947
|
-
checked = checkedMemoryDirectory(vaultBase, checked.target, 'outbox de memória', {
|
|
948
|
-
allowMissing: false,
|
|
949
|
-
});
|
|
950
|
-
return readdirSync(checked.target, { withFileTypes: true })
|
|
951
|
-
.filter((entry) => entry.name.endsWith('.json'))
|
|
952
|
-
.map((entry) => {
|
|
953
|
-
assertVaultPathSafe(vaultBase, join(checked.target, entry.name), {
|
|
954
|
-
allowMissing: false,
|
|
955
|
-
label: `entrada ${entry.name} do outbox de memória`,
|
|
956
|
-
});
|
|
957
|
-
return entry;
|
|
958
|
-
}).length;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
function appendLedgerDurably(vaultBase, path, events) {
|
|
962
|
-
if (!events.length) return;
|
|
963
|
-
let fd;
|
|
964
|
-
try {
|
|
965
|
-
let checked = checkedMemoryFile(vaultBase, path, 'ledger MEMORY_EVENTS.jsonl');
|
|
966
|
-
checked = checkedMemoryFile(vaultBase, checked.target, 'ledger MEMORY_EVENTS.jsonl');
|
|
967
|
-
fd = openSync(checked.target, 'a');
|
|
968
|
-
assertOpenedMemoryFile(vaultBase, checked.target, fd, 'ledger MEMORY_EVENTS.jsonl');
|
|
969
|
-
const payload = events.map((item) => canonicalMemoryJson(item)).join('\n') + '\n';
|
|
970
|
-
writeFileSync(fd, payload, 'utf8');
|
|
971
|
-
fsyncSync(fd);
|
|
972
|
-
assertOpenedMemoryFile(vaultBase, checked.target, fd, 'ledger MEMORY_EVENTS.jsonl');
|
|
973
|
-
} finally {
|
|
974
|
-
if (fd !== undefined) closeSync(fd);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
function writeAtomicIfChanged(vaultBase, path, content, label) {
|
|
979
|
-
const checked = checkedMemoryFile(vaultBase, path, label);
|
|
980
|
-
if (checked.exists) {
|
|
981
|
-
try {
|
|
982
|
-
if (readCheckedMemoryFile(vaultBase, checked.target, 'utf8', label) === content) return false;
|
|
983
|
-
} catch (error) {
|
|
984
|
-
if (error?.code === 'VAULT_PATH_UNSAFE') throw error;
|
|
985
|
-
// A contained but unreadable projection may be atomically replaced.
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
writeVaultFileAtomic(vaultBase, checked.target, content, 'utf8', { label });
|
|
989
|
-
return true;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
function injectFault(faultAt, boundary) {
|
|
993
|
-
if (faultAt === boundary) throw new Error(`Injected memory-store fault: ${boundary}`);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
/** Build every derived byte from an immutable authority snapshot without publishing it. */
|
|
997
|
-
export function prepareMemoryProjection(vaultBase, allEvents) {
|
|
998
|
-
const reduced = deriveMemoryProjection(vaultBase, allEvents);
|
|
999
|
-
const updatedAt = allEvents
|
|
1000
|
-
.map((item) => item.observed_at)
|
|
1001
|
-
.filter(Boolean)
|
|
1002
|
-
.sort()
|
|
1003
|
-
.at(-1);
|
|
1004
|
-
const shared = renderSharedMemory({
|
|
1005
|
-
revision: reduced.revision,
|
|
1006
|
-
eventCursor: reduced.ledgerCursor,
|
|
1007
|
-
events: reduced.activeEvents,
|
|
1008
|
-
stateHash: reduced.stateHash,
|
|
1009
|
-
updatedAt,
|
|
1010
|
-
});
|
|
1011
|
-
const sharedMetadata = parseSharedMemory(shared).metadata;
|
|
1012
|
-
const candidates = reduced.candidates.map((item) => canonicalMemoryJson(item)).join('\n')
|
|
1013
|
-
+ (reduced.candidates.length ? '\n' : '');
|
|
1014
|
-
return {
|
|
1015
|
-
sharedContent: shared,
|
|
1016
|
-
candidatesContent: candidates,
|
|
1017
|
-
revision: reduced.revision,
|
|
1018
|
-
eventCursor: reduced.eventCursor,
|
|
1019
|
-
ledgerCursor: reduced.ledgerCursor,
|
|
1020
|
-
stateHash: reduced.stateHash,
|
|
1021
|
-
checkpoint: reduced.checkpoint,
|
|
1022
|
-
candidates: reduced.candidates.length,
|
|
1023
|
-
projectedEvents: sharedMetadata.projected_events ?? reduced.activeEvents.length,
|
|
1024
|
-
omittedEvents: sharedMetadata.omitted_events ?? 0,
|
|
1025
|
-
};
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
/** Publish a projection prepared from the same locked authority snapshot. */
|
|
1029
|
-
export function publishMemoryProjection(vaultBase, prepared) {
|
|
1030
|
-
const {
|
|
1031
|
-
sharedContent, candidatesContent, ...projection
|
|
1032
|
-
} = prepared;
|
|
1033
|
-
// Validate the complete publication set before the first rename: a bad candidates
|
|
1034
|
-
// alias cannot leave SHARED partially advanced (and vice versa).
|
|
1035
|
-
preflightProjectionTargets(vaultBase);
|
|
1036
|
-
const sharedWritten = writeAtomicIfChanged(
|
|
1037
|
-
vaultBase, sharedPath(vaultBase), sharedContent, 'projeção SHARED_MEMORY.md',
|
|
1038
|
-
);
|
|
1039
|
-
const candidatesWritten = writeAtomicIfChanged(
|
|
1040
|
-
vaultBase, candidatesPath(vaultBase), candidatesContent, 'projeção MEMORY_CANDIDATES.jsonl',
|
|
1041
|
-
);
|
|
1042
|
-
return { ...projection, projectionsWritten: sharedWritten || candidatesWritten };
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
function publishDerivedProjection(vaultBase, allEvents) {
|
|
1046
|
-
return publishMemoryProjection(vaultBase, prepareMemoryProjection(vaultBase, allEvents));
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
// Reconciliation needs to validate proof, perform registry CAS, and publish the exact
|
|
1050
|
-
// prepared bytes inside one MEMORY critical section. This primitive deliberately does
|
|
1051
|
-
// not create `.brain`: callers must complete their read-only Vault preflight first.
|
|
1052
|
-
export const MEMORY_LOCK_BUSY = VAULT_LOCK_BUSY;
|
|
1053
|
-
export function withMemoryLock(vaultBase, fn, options = {}) {
|
|
1054
|
-
return withVaultPathLock(vaultBase, lockTarget(vaultBase), fn, options);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
function projectLocked(vaultBase, { faultAt } = {}) {
|
|
1058
|
-
const ledger = readMemoryLedger(vaultBase);
|
|
1059
|
-
if (ledger.status !== 'ok') throw new MemoryLedgerCorruption(ledger.errors);
|
|
1060
|
-
const outbox = readOutbox(vaultBase);
|
|
1061
|
-
const ledgerById = new Map(ledger.events.map((item) => [item.event_id, item]));
|
|
1062
|
-
const newEvents = [];
|
|
1063
|
-
|
|
1064
|
-
for (const { event: item } of outbox) {
|
|
1065
|
-
const existing = ledgerById.get(item.event_id);
|
|
1066
|
-
if (existing) {
|
|
1067
|
-
if (canonicalMemoryJson(existing) !== canonicalMemoryJson(item)) throw new MemoryEventCollision(item.event_id);
|
|
1068
|
-
continue;
|
|
1069
|
-
}
|
|
1070
|
-
ledgerById.set(item.event_id, item);
|
|
1071
|
-
newEvents.push(item);
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
const allEvents = [...ledger.events, ...newEvents];
|
|
1075
|
-
const prepared = prepareMemoryProjection(vaultBase, allEvents);
|
|
1076
|
-
// CORE and both sidecars are checked before the append, so an unsafe derived target
|
|
1077
|
-
// cannot partially advance the authoritative ledger.
|
|
1078
|
-
preflightProjectionTargets(vaultBase);
|
|
1079
|
-
appendLedgerDurably(vaultBase, ledger.path, newEvents);
|
|
1080
|
-
injectFault(faultAt, 'after-ledger');
|
|
1081
|
-
|
|
1082
|
-
const projection = publishMemoryProjection(vaultBase, prepared);
|
|
1083
|
-
injectFault(faultAt, 'after-projection');
|
|
1084
|
-
|
|
1085
|
-
const consumedEventIds = [];
|
|
1086
|
-
for (const entry of outbox) {
|
|
1087
|
-
unlinkVaultFile(vaultBase, entry.path, {
|
|
1088
|
-
missingOk: false, label: 'evento consumido do outbox de memória',
|
|
1089
|
-
});
|
|
1090
|
-
consumedEventIds.push(entry.event.event_id);
|
|
1091
|
-
}
|
|
1092
|
-
return {
|
|
1093
|
-
status: 'projected',
|
|
1094
|
-
appended: newEvents.length,
|
|
1095
|
-
consumed: outbox.length,
|
|
1096
|
-
consumedEventIds,
|
|
1097
|
-
pending: 0,
|
|
1098
|
-
...projection,
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
/** Serialize ledger append + full deterministic replay under .brain/MEMORY.lock. */
|
|
1103
|
-
export function projectMemoryOutbox(vaultBase, options = {}) {
|
|
1104
|
-
mkdirVaultPath(vaultBase, brainDir(vaultBase), { label: 'raiz .brain da memória' });
|
|
1105
|
-
const pending = countPendingOutbox(vaultBase);
|
|
1106
|
-
const result = withVaultPathLock(
|
|
1107
|
-
vaultBase,
|
|
1108
|
-
lockTarget(vaultBase),
|
|
1109
|
-
() => projectLocked(vaultBase, options),
|
|
1110
|
-
options.lock || {},
|
|
1111
|
-
);
|
|
1112
|
-
if (result === VAULT_LOCK_BUSY) return { status: 'busy', pending };
|
|
1113
|
-
return result;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
/**
|
|
1117
|
-
* Rebuild generated projections from the existing ledger only. This path never
|
|
1118
|
-
* enumerates, reads, acknowledges, or consumes producer outbox files.
|
|
1119
|
-
*/
|
|
1120
|
-
export function reprojectMemoryLedger(vaultBase, options = {}) {
|
|
1121
|
-
mkdirVaultPath(vaultBase, brainDir(vaultBase), { label: 'raiz .brain da memória' });
|
|
1122
|
-
const result = withVaultPathLock(vaultBase, lockTarget(vaultBase), () => {
|
|
1123
|
-
const ledger = readMemoryLedger(vaultBase);
|
|
1124
|
-
if (ledger.status !== 'ok') throw new MemoryLedgerCorruption(ledger.errors);
|
|
1125
|
-
return {
|
|
1126
|
-
status: 'reprojected',
|
|
1127
|
-
...publishDerivedProjection(vaultBase, ledger.events),
|
|
1128
|
-
};
|
|
1129
|
-
}, options.lock || {});
|
|
1130
|
-
if (result === VAULT_LOCK_BUSY) return { status: 'busy' };
|
|
1131
|
-
return result;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/** Explicit repair: preserve exact corrupt bytes, then retain every independently valid line. */
|
|
1135
|
-
export function repairMemoryLedger(vaultBase, options = {}) {
|
|
1136
|
-
mkdirVaultPath(vaultBase, brainDir(vaultBase), { label: 'raiz .brain da memória' });
|
|
1137
|
-
const result = withVaultPathLock(vaultBase, lockTarget(vaultBase), () => {
|
|
1138
|
-
const ledger = readMemoryLedger(vaultBase);
|
|
1139
|
-
if (ledger.status === 'ok') return { status: 'unchanged', repairedLines: 0, backupPath: null };
|
|
1140
|
-
const backupPath = `${ledger.path}.corrupt-${Date.now()}.bak`;
|
|
1141
|
-
// copyFileSync preserves the exact evidence before any canonical rewrite.
|
|
1142
|
-
checkedMemoryFile(vaultBase, ledger.path, 'ledger corrompido', { allowMissing: false });
|
|
1143
|
-
checkedMemoryFile(vaultBase, backupPath, 'backup do ledger corrompido', { mustNotExist: true });
|
|
1144
|
-
// Revalidate immediately before the exclusive copy, then verify the created inode.
|
|
1145
|
-
checkedMemoryFile(vaultBase, ledger.path, 'ledger corrompido', { allowMissing: false });
|
|
1146
|
-
checkedMemoryFile(vaultBase, backupPath, 'backup do ledger corrompido', { mustNotExist: true });
|
|
1147
|
-
copyFileSync(ledger.path, backupPath, fsConstants.COPYFILE_EXCL);
|
|
1148
|
-
checkedMemoryFile(vaultBase, backupPath, 'backup do ledger corrompido', { allowMissing: false });
|
|
1149
|
-
const repaired = ledger.events.map((item) => canonicalMemoryJson(item)).join('\n')
|
|
1150
|
-
+ (ledger.events.length ? '\n' : '');
|
|
1151
|
-
writeVaultFileAtomic(vaultBase, ledger.path, repaired, 'utf8', { label: 'ledger reparado' });
|
|
1152
|
-
return {
|
|
1153
|
-
status: 'repaired',
|
|
1154
|
-
repairedLines: ledger.errors.length,
|
|
1155
|
-
retainedEvents: ledger.events.length,
|
|
1156
|
-
backupPath,
|
|
1157
|
-
};
|
|
1158
|
-
}, options.lock || {});
|
|
1159
|
-
if (result === VAULT_LOCK_BUSY) return { status: 'busy', repairedLines: 0, backupPath: null };
|
|
1160
|
-
return result;
|
|
1161
|
-
}
|
|
1
|
+
export * from './memory-store-core.mjs';
|
|
2
|
+
export {
|
|
3
|
+
MEMORY_SNAPSHOT_FILE,
|
|
4
|
+
MEMORY_SNAPSHOT_REDUCER_VERSION,
|
|
5
|
+
MEMORY_SNAPSHOT_SCHEMA_VERSION,
|
|
6
|
+
projectMemoryOutbox,
|
|
7
|
+
readMemoryProjectionSnapshot,
|
|
8
|
+
reprojectMemoryLedger,
|
|
9
|
+
} from './memory-snapshot-store.mjs';
|
|
10
|
+
export {
|
|
11
|
+
MEMORY_SEGMENT_DEFAULT_MAX_BYTES,
|
|
12
|
+
MEMORY_SEGMENT_DEFAULT_MAX_EVENTS,
|
|
13
|
+
MEMORY_SEGMENT_DIRECTORY,
|
|
14
|
+
MEMORY_SEGMENT_MANIFEST_FILE,
|
|
15
|
+
MEMORY_SEGMENT_MANIFEST_SCHEMA_VERSION,
|
|
16
|
+
MEMORY_SEGMENT_SCHEMA_VERSION,
|
|
17
|
+
MemorySegmentCorruption,
|
|
18
|
+
readMemorySegmentManifest,
|
|
19
|
+
repairMemorySegmentManifest,
|
|
20
|
+
sealMemorySegments,
|
|
21
|
+
verifyMemorySegments,
|
|
22
|
+
} from './memory-segment-store.mjs';
|
|
23
|
+
export {
|
|
24
|
+
MEMORY_LEDGER_BACKUP_DIRECTORY,
|
|
25
|
+
MEMORY_LEDGER_GENERATION_FILE,
|
|
26
|
+
MEMORY_LEDGER_GENERATION_SCHEMA_VERSION,
|
|
27
|
+
MEMORY_ROTATION_JOURNAL_FILE,
|
|
28
|
+
MEMORY_ROTATION_RECEIPT_CHECKPOINT_FILE,
|
|
29
|
+
MEMORY_ROTATION_RECEIPTS_FILE,
|
|
30
|
+
MemoryLedgerGenerationCorruption,
|
|
31
|
+
MemoryRotationReceiptCorruption,
|
|
32
|
+
memoryLedgerGenerationStatus,
|
|
33
|
+
readMemoryLedgerGeneration,
|
|
34
|
+
readMemoryRotationJournal,
|
|
35
|
+
readMemoryRotationReceipts,
|
|
36
|
+
} from './memory-ledger-view.mjs';
|
|
37
|
+
export {
|
|
38
|
+
MEMORY_ROTATION_CANDIDATE_PREFIX,
|
|
39
|
+
MEMORY_ROTATION_POLICY,
|
|
40
|
+
MemoryLedgerRotationBlocked,
|
|
41
|
+
compactMemoryLedger,
|
|
42
|
+
memoryLedgerRotationStatus,
|
|
43
|
+
planMemoryLedgerRotation,
|
|
44
|
+
recoverMemoryLedgerRotation,
|
|
45
|
+
rotateMemoryLedger,
|
|
46
|
+
} from './memory-rotation-store.mjs';
|