@cleocode/core 2026.4.58 → 2026.4.60
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/dist/index.js +488 -11
- package/dist/index.js.map +3 -3
- package/dist/internal.js +497 -13
- package/dist/internal.js.map +3 -3
- package/dist/memory/graph-queries.d.ts.map +1 -1
- package/dist/store/agent-registry-accessor.d.ts +1 -1
- package/dist/store/brain-accessor.d.ts +57 -1
- package/dist/store/brain-accessor.d.ts.map +1 -1
- package/dist/store/brain-schema.d.ts +738 -0
- package/dist/store/brain-schema.d.ts.map +1 -1
- package/dist/store/brain-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-schema.d.ts +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/validation/doctor/checks.d.ts +7 -0
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/migrations/drizzle-brain/20260416000001_t673-retrieval-log-plasticity-columns/migration.sql +57 -0
- package/migrations/drizzle-brain/20260416000002_t673-plasticity-events-expand/migration.sql +44 -0
- package/migrations/drizzle-brain/20260416000003_t673-page-edges-plasticity-columns/migration.sql +44 -0
- package/migrations/drizzle-brain/20260416000004_t673-new-plasticity-tables/migration.sql +73 -0
- package/package.json +8 -8
- package/src/memory/__tests__/brain-retrieval-m1.test.ts +250 -0
- package/src/memory/__tests__/brain-schema-m2-m3.test.ts +418 -0
- package/src/memory/__tests__/brain-schema-m4.test.ts +494 -0
- package/src/memory/brain-retrieval.ts +1 -1
- package/src/memory/graph-queries.ts +14 -0
- package/src/store/agent-registry-accessor.ts +1 -1
- package/src/store/brain-accessor.ts +120 -0
- package/src/store/brain-schema.ts +373 -1
- package/src/store/brain-sqlite.ts +123 -0
- package/src/system/health.ts +4 -1
- package/src/validation/doctor/checks.ts +107 -0
- package/src/validation/protocols/protocols-markdown/research.md +1 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functional tests for STDP M2 (brain_plasticity_events expansion) and
|
|
3
|
+
* M3 (brain_page_edges plasticity columns) migrations.
|
|
4
|
+
*
|
|
5
|
+
* Uses a real in-memory SQLite database via getBrainDb() with a tmpdir
|
|
6
|
+
* CLEO_DIR. No vi.mock() on any brain or SQLite module.
|
|
7
|
+
*
|
|
8
|
+
* Test plan:
|
|
9
|
+
* M2-1: All 5 new columns exist in brain_plasticity_events after migration
|
|
10
|
+
* M2-2: INSERT with new columns succeeds; values round-trip correctly
|
|
11
|
+
* M2-3: INSERT without new columns also succeeds (defaults/nulls)
|
|
12
|
+
* M3-1: All 6 new columns exist in brain_page_edges after migration
|
|
13
|
+
* M3-2: INSERT with new plasticity columns succeeds; values round-trip
|
|
14
|
+
* M3-3: INSERT without new columns succeeds (defaults apply: 0, 'static')
|
|
15
|
+
* M3-4: plasticity_class enum values 'static', 'hebbian', 'stdp' are accepted
|
|
16
|
+
* M3-5: Seed UPDATE sets plasticity_class='hebbian' for co_retrieved edges
|
|
17
|
+
*
|
|
18
|
+
* @task T696 (M2)
|
|
19
|
+
* @task T706 (M3)
|
|
20
|
+
* @epic T627
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
24
|
+
import { tmpdir } from 'node:os';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
27
|
+
|
|
28
|
+
vi.setConfig({ testTimeout: 30_000 });
|
|
29
|
+
|
|
30
|
+
// We do NOT mock brain-sqlite.js — this is a functional test.
|
|
31
|
+
|
|
32
|
+
let tempDir: string;
|
|
33
|
+
|
|
34
|
+
beforeEach(async () => {
|
|
35
|
+
tempDir = await mkdtemp(join(tmpdir(), 'cleo-brain-m2-m3-'));
|
|
36
|
+
const cleoDir = join(tempDir, '.cleo');
|
|
37
|
+
const { mkdirSync } = await import('node:fs');
|
|
38
|
+
mkdirSync(cleoDir, { recursive: true });
|
|
39
|
+
process.env['CLEO_DIR'] = cleoDir;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(async () => {
|
|
43
|
+
const { closeBrainDb } = await import('../../store/brain-sqlite.js');
|
|
44
|
+
closeBrainDb();
|
|
45
|
+
delete process.env['CLEO_DIR'];
|
|
46
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
47
|
+
// Reset module singleton so next test gets a fresh DB
|
|
48
|
+
const { resetBrainDbState } = await import('../../store/brain-sqlite.js');
|
|
49
|
+
resetBrainDbState();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Helper: get PRAGMA table_info columns as a name→type map
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
async function getTableColumns(tableName: string): Promise<Map<string, string>> {
|
|
56
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
57
|
+
const nativeDb = getBrainNativeDb();
|
|
58
|
+
if (!nativeDb) throw new Error('nativeDb is null');
|
|
59
|
+
type PragmaRow = { name: string; type: string };
|
|
60
|
+
const rows = nativeDb.prepare(`PRAGMA table_info(${tableName})`).all() as PragmaRow[];
|
|
61
|
+
const map = new Map<string, string>();
|
|
62
|
+
for (const row of rows) {
|
|
63
|
+
map.set(row.name, row.type.toUpperCase());
|
|
64
|
+
}
|
|
65
|
+
return map;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Helper: initialize brain DB (runs migrations, returns db handle)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
async function openDb() {
|
|
72
|
+
const { getBrainDb } = await import('../../store/brain-sqlite.js');
|
|
73
|
+
return getBrainDb(tempDir);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ===========================================================================
|
|
77
|
+
// M2: brain_plasticity_events — 5 new observability columns
|
|
78
|
+
// ===========================================================================
|
|
79
|
+
|
|
80
|
+
describe('M2 — brain_plasticity_events expansion', () => {
|
|
81
|
+
it('M2-1: all 5 new columns exist after migration', async () => {
|
|
82
|
+
await openDb();
|
|
83
|
+
const cols = await getTableColumns('brain_plasticity_events');
|
|
84
|
+
|
|
85
|
+
expect(cols.has('weight_before'), 'weight_before missing').toBe(true);
|
|
86
|
+
expect(cols.has('weight_after'), 'weight_after missing').toBe(true);
|
|
87
|
+
expect(cols.has('retrieval_log_id'), 'retrieval_log_id missing').toBe(true);
|
|
88
|
+
expect(cols.has('reward_signal'), 'reward_signal missing').toBe(true);
|
|
89
|
+
expect(cols.has('delta_t_ms'), 'delta_t_ms missing').toBe(true);
|
|
90
|
+
|
|
91
|
+
// Verify column types
|
|
92
|
+
expect(cols.get('weight_before')).toBe('REAL');
|
|
93
|
+
expect(cols.get('weight_after')).toBe('REAL');
|
|
94
|
+
expect(cols.get('retrieval_log_id')).toBe('INTEGER');
|
|
95
|
+
expect(cols.get('reward_signal')).toBe('REAL');
|
|
96
|
+
expect(cols.get('delta_t_ms')).toBe('INTEGER');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('M2-2: INSERT with all new columns round-trips correctly', async () => {
|
|
100
|
+
await openDb();
|
|
101
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
102
|
+
const nativeDb = getBrainNativeDb()!;
|
|
103
|
+
|
|
104
|
+
nativeDb
|
|
105
|
+
.prepare(
|
|
106
|
+
`INSERT INTO brain_plasticity_events
|
|
107
|
+
(source_node, target_node, delta_w, kind, session_id,
|
|
108
|
+
weight_before, weight_after, retrieval_log_id, reward_signal, delta_t_ms)
|
|
109
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
110
|
+
)
|
|
111
|
+
.run('obs:A', 'obs:B', 0.05, 'ltp', 'ses_test_001', 0.8, 0.85, 42, 1.0, 15000);
|
|
112
|
+
|
|
113
|
+
type EventRow = {
|
|
114
|
+
source_node: string;
|
|
115
|
+
target_node: string;
|
|
116
|
+
delta_w: number;
|
|
117
|
+
kind: string;
|
|
118
|
+
weight_before: number | null;
|
|
119
|
+
weight_after: number | null;
|
|
120
|
+
retrieval_log_id: number | null;
|
|
121
|
+
reward_signal: number | null;
|
|
122
|
+
delta_t_ms: number | null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const row = nativeDb
|
|
126
|
+
.prepare(
|
|
127
|
+
`SELECT source_node, target_node, delta_w, kind,
|
|
128
|
+
weight_before, weight_after, retrieval_log_id, reward_signal, delta_t_ms
|
|
129
|
+
FROM brain_plasticity_events WHERE source_node = 'obs:A'`,
|
|
130
|
+
)
|
|
131
|
+
.get() as EventRow;
|
|
132
|
+
|
|
133
|
+
expect(row).not.toBeNull();
|
|
134
|
+
expect(row.source_node).toBe('obs:A');
|
|
135
|
+
expect(row.target_node).toBe('obs:B');
|
|
136
|
+
expect(row.delta_w).toBe(0.05);
|
|
137
|
+
expect(row.kind).toBe('ltp');
|
|
138
|
+
expect(row.weight_before).toBe(0.8);
|
|
139
|
+
expect(row.weight_after).toBe(0.85);
|
|
140
|
+
expect(row.retrieval_log_id).toBe(42);
|
|
141
|
+
expect(row.reward_signal).toBe(1.0);
|
|
142
|
+
expect(row.delta_t_ms).toBe(15000);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('M2-3: INSERT without new columns succeeds (nulls for new cols)', async () => {
|
|
146
|
+
await openDb();
|
|
147
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
148
|
+
const nativeDb = getBrainNativeDb()!;
|
|
149
|
+
|
|
150
|
+
// Old-style INSERT — no new columns
|
|
151
|
+
nativeDb
|
|
152
|
+
.prepare(
|
|
153
|
+
`INSERT INTO brain_plasticity_events
|
|
154
|
+
(source_node, target_node, delta_w, kind)
|
|
155
|
+
VALUES (?, ?, ?, ?)`,
|
|
156
|
+
)
|
|
157
|
+
.run('obs:C', 'obs:D', -0.06, 'ltd');
|
|
158
|
+
|
|
159
|
+
type EventRow = {
|
|
160
|
+
source_node: string;
|
|
161
|
+
weight_before: number | null;
|
|
162
|
+
weight_after: number | null;
|
|
163
|
+
retrieval_log_id: number | null;
|
|
164
|
+
reward_signal: number | null;
|
|
165
|
+
delta_t_ms: number | null;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const row = nativeDb
|
|
169
|
+
.prepare(
|
|
170
|
+
`SELECT source_node, weight_before, weight_after,
|
|
171
|
+
retrieval_log_id, reward_signal, delta_t_ms
|
|
172
|
+
FROM brain_plasticity_events WHERE source_node = 'obs:C'`,
|
|
173
|
+
)
|
|
174
|
+
.get() as EventRow;
|
|
175
|
+
|
|
176
|
+
expect(row).not.toBeNull();
|
|
177
|
+
// All new columns should be null (no default set)
|
|
178
|
+
expect(row.weight_before).toBeNull();
|
|
179
|
+
expect(row.weight_after).toBeNull();
|
|
180
|
+
expect(row.retrieval_log_id).toBeNull();
|
|
181
|
+
expect(row.reward_signal).toBeNull();
|
|
182
|
+
expect(row.delta_t_ms).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('M2-4: indexes on new columns exist', async () => {
|
|
186
|
+
await openDb();
|
|
187
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
188
|
+
const nativeDb = getBrainNativeDb()!;
|
|
189
|
+
|
|
190
|
+
type IndexRow = { name: string };
|
|
191
|
+
const indexes = nativeDb
|
|
192
|
+
.prepare(`PRAGMA index_list(brain_plasticity_events)`)
|
|
193
|
+
.all() as IndexRow[];
|
|
194
|
+
const names = indexes.map((r) => r.name);
|
|
195
|
+
|
|
196
|
+
expect(names).toContain('idx_plasticity_retrieval_log');
|
|
197
|
+
expect(names).toContain('idx_plasticity_reward');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ===========================================================================
|
|
202
|
+
// M3: brain_page_edges — 6 new plasticity tracking columns
|
|
203
|
+
// ===========================================================================
|
|
204
|
+
|
|
205
|
+
describe('M3 — brain_page_edges plasticity columns', () => {
|
|
206
|
+
it('M3-1: all 6 new columns exist after migration', async () => {
|
|
207
|
+
await openDb();
|
|
208
|
+
const cols = await getTableColumns('brain_page_edges');
|
|
209
|
+
|
|
210
|
+
expect(cols.has('last_reinforced_at'), 'last_reinforced_at missing').toBe(true);
|
|
211
|
+
expect(cols.has('reinforcement_count'), 'reinforcement_count missing').toBe(true);
|
|
212
|
+
expect(cols.has('plasticity_class'), 'plasticity_class missing').toBe(true);
|
|
213
|
+
expect(cols.has('last_depressed_at'), 'last_depressed_at missing').toBe(true);
|
|
214
|
+
expect(cols.has('depression_count'), 'depression_count missing').toBe(true);
|
|
215
|
+
expect(cols.has('stability_score'), 'stability_score missing').toBe(true);
|
|
216
|
+
|
|
217
|
+
// Verify column types
|
|
218
|
+
expect(cols.get('last_reinforced_at')).toBe('TEXT');
|
|
219
|
+
expect(cols.get('reinforcement_count')).toBe('INTEGER');
|
|
220
|
+
expect(cols.get('plasticity_class')).toBe('TEXT');
|
|
221
|
+
expect(cols.get('last_depressed_at')).toBe('TEXT');
|
|
222
|
+
expect(cols.get('depression_count')).toBe('INTEGER');
|
|
223
|
+
expect(cols.get('stability_score')).toBe('REAL');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('M3-2: INSERT with all new plasticity columns round-trips correctly', async () => {
|
|
227
|
+
await openDb();
|
|
228
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
229
|
+
const nativeDb = getBrainNativeDb()!;
|
|
230
|
+
|
|
231
|
+
// First insert nodes (brain_page_edges requires from_id/to_id but NOT FK constrained)
|
|
232
|
+
const now = new Date().toISOString();
|
|
233
|
+
nativeDb
|
|
234
|
+
.prepare(
|
|
235
|
+
`INSERT INTO brain_page_edges
|
|
236
|
+
(from_id, to_id, edge_type, weight,
|
|
237
|
+
last_reinforced_at, reinforcement_count, plasticity_class,
|
|
238
|
+
last_depressed_at, depression_count, stability_score)
|
|
239
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
240
|
+
)
|
|
241
|
+
.run('obs:X', 'obs:Y', 'co_retrieved', 0.75, now, 5, 'stdp', null, 0, 0.62);
|
|
242
|
+
|
|
243
|
+
type EdgeRow = {
|
|
244
|
+
from_id: string;
|
|
245
|
+
to_id: string;
|
|
246
|
+
last_reinforced_at: string | null;
|
|
247
|
+
reinforcement_count: number;
|
|
248
|
+
plasticity_class: string;
|
|
249
|
+
last_depressed_at: string | null;
|
|
250
|
+
depression_count: number;
|
|
251
|
+
stability_score: number | null;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const row = nativeDb
|
|
255
|
+
.prepare(
|
|
256
|
+
`SELECT from_id, to_id, last_reinforced_at, reinforcement_count,
|
|
257
|
+
plasticity_class, last_depressed_at, depression_count, stability_score
|
|
258
|
+
FROM brain_page_edges WHERE from_id = 'obs:X' AND to_id = 'obs:Y'`,
|
|
259
|
+
)
|
|
260
|
+
.get() as EdgeRow;
|
|
261
|
+
|
|
262
|
+
expect(row).not.toBeNull();
|
|
263
|
+
expect(row.last_reinforced_at).toBe(now);
|
|
264
|
+
expect(row.reinforcement_count).toBe(5);
|
|
265
|
+
expect(row.plasticity_class).toBe('stdp');
|
|
266
|
+
expect(row.last_depressed_at).toBeNull();
|
|
267
|
+
expect(row.depression_count).toBe(0);
|
|
268
|
+
expect(row.stability_score).toBeCloseTo(0.62, 5);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('M3-3: INSERT without new columns uses correct defaults (0, static)', async () => {
|
|
272
|
+
await openDb();
|
|
273
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
274
|
+
const nativeDb = getBrainNativeDb()!;
|
|
275
|
+
|
|
276
|
+
// Old-style INSERT — no plasticity columns
|
|
277
|
+
nativeDb
|
|
278
|
+
.prepare(
|
|
279
|
+
`INSERT INTO brain_page_edges (from_id, to_id, edge_type, weight)
|
|
280
|
+
VALUES (?, ?, ?, ?)`,
|
|
281
|
+
)
|
|
282
|
+
.run('obs:M', 'obs:N', 'contains', 1.0);
|
|
283
|
+
|
|
284
|
+
type EdgeRow = {
|
|
285
|
+
reinforcement_count: number;
|
|
286
|
+
plasticity_class: string;
|
|
287
|
+
depression_count: number;
|
|
288
|
+
last_reinforced_at: string | null;
|
|
289
|
+
last_depressed_at: string | null;
|
|
290
|
+
stability_score: number | null;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const row = nativeDb
|
|
294
|
+
.prepare(
|
|
295
|
+
`SELECT reinforcement_count, plasticity_class, depression_count,
|
|
296
|
+
last_reinforced_at, last_depressed_at, stability_score
|
|
297
|
+
FROM brain_page_edges WHERE from_id = 'obs:M' AND to_id = 'obs:N'`,
|
|
298
|
+
)
|
|
299
|
+
.get() as EdgeRow;
|
|
300
|
+
|
|
301
|
+
expect(row).not.toBeNull();
|
|
302
|
+
expect(row.reinforcement_count).toBe(0);
|
|
303
|
+
expect(row.plasticity_class).toBe('static');
|
|
304
|
+
expect(row.depression_count).toBe(0);
|
|
305
|
+
expect(row.last_reinforced_at).toBeNull();
|
|
306
|
+
expect(row.last_depressed_at).toBeNull();
|
|
307
|
+
expect(row.stability_score).toBeNull();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('M3-4: plasticity_class accepts all three valid enum values', async () => {
|
|
311
|
+
await openDb();
|
|
312
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
313
|
+
const nativeDb = getBrainNativeDb()!;
|
|
314
|
+
|
|
315
|
+
const cases = [
|
|
316
|
+
['obs:P1', 'obs:P2', 'static'],
|
|
317
|
+
['obs:P3', 'obs:P4', 'hebbian'],
|
|
318
|
+
['obs:P5', 'obs:P6', 'stdp'],
|
|
319
|
+
] as const;
|
|
320
|
+
|
|
321
|
+
for (const [from, to, cls] of cases) {
|
|
322
|
+
expect(() =>
|
|
323
|
+
nativeDb
|
|
324
|
+
.prepare(
|
|
325
|
+
`INSERT INTO brain_page_edges (from_id, to_id, edge_type, weight, plasticity_class)
|
|
326
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
327
|
+
)
|
|
328
|
+
.run(from, to, 'co_retrieved', 0.5, cls),
|
|
329
|
+
).not.toThrow();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
type EdgeRow = { from_id: string; plasticity_class: string };
|
|
333
|
+
const rows = nativeDb
|
|
334
|
+
.prepare(
|
|
335
|
+
`SELECT from_id, plasticity_class FROM brain_page_edges
|
|
336
|
+
WHERE from_id IN ('obs:P1','obs:P3','obs:P5')
|
|
337
|
+
ORDER BY from_id`,
|
|
338
|
+
)
|
|
339
|
+
.all() as EdgeRow[];
|
|
340
|
+
|
|
341
|
+
expect(rows).toHaveLength(3);
|
|
342
|
+
expect(rows[0]!.plasticity_class).toBe('static');
|
|
343
|
+
expect(rows[1]!.plasticity_class).toBe('hebbian');
|
|
344
|
+
expect(rows[2]!.plasticity_class).toBe('stdp');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('M3-5: co_retrieved edges are seeded as hebbian by migration', async () => {
|
|
348
|
+
// Insert a co_retrieved edge BEFORE opening the DB (so migration seeds it)
|
|
349
|
+
// We can't do this — migration runs at DB open. Instead, verify that
|
|
350
|
+
// inserting a co_retrieved edge and then re-opening picks up 'static' default,
|
|
351
|
+
// but the ensureColumns guard seeds existing ones.
|
|
352
|
+
//
|
|
353
|
+
// Since we can't pre-populate before migrations, this test verifies that
|
|
354
|
+
// after opening, a manually-inserted co_retrieved edge can be updated
|
|
355
|
+
// to 'hebbian' as the seed would — and that the column exists with correct default.
|
|
356
|
+
await openDb();
|
|
357
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
358
|
+
const nativeDb = getBrainNativeDb()!;
|
|
359
|
+
|
|
360
|
+
// Insert co_retrieved edge (gets default 'static')
|
|
361
|
+
nativeDb
|
|
362
|
+
.prepare(
|
|
363
|
+
`INSERT INTO brain_page_edges (from_id, to_id, edge_type, weight)
|
|
364
|
+
VALUES (?, ?, ?, ?)`,
|
|
365
|
+
)
|
|
366
|
+
.run('obs:Q1', 'obs:Q2', 'co_retrieved', 0.3);
|
|
367
|
+
|
|
368
|
+
type EdgeRow = { plasticity_class: string };
|
|
369
|
+
|
|
370
|
+
// Verify default is 'static' on fresh insert
|
|
371
|
+
const before = nativeDb
|
|
372
|
+
.prepare(
|
|
373
|
+
`SELECT plasticity_class FROM brain_page_edges
|
|
374
|
+
WHERE from_id = 'obs:Q1' AND to_id = 'obs:Q2'`,
|
|
375
|
+
)
|
|
376
|
+
.get() as EdgeRow;
|
|
377
|
+
expect(before.plasticity_class).toBe('static');
|
|
378
|
+
|
|
379
|
+
// Apply seed logic (the ensureColumns guard does this for pre-existing rows)
|
|
380
|
+
nativeDb
|
|
381
|
+
.prepare(
|
|
382
|
+
`UPDATE brain_page_edges SET plasticity_class = 'hebbian'
|
|
383
|
+
WHERE edge_type = 'co_retrieved' AND plasticity_class = 'static'`,
|
|
384
|
+
)
|
|
385
|
+
.run();
|
|
386
|
+
|
|
387
|
+
const after = nativeDb
|
|
388
|
+
.prepare(
|
|
389
|
+
`SELECT plasticity_class FROM brain_page_edges
|
|
390
|
+
WHERE from_id = 'obs:Q1' AND to_id = 'obs:Q2'`,
|
|
391
|
+
)
|
|
392
|
+
.get() as EdgeRow;
|
|
393
|
+
expect(after.plasticity_class).toBe('hebbian');
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('M3-6: indexes on new plasticity columns exist', async () => {
|
|
397
|
+
await openDb();
|
|
398
|
+
const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
|
|
399
|
+
const nativeDb = getBrainNativeDb()!;
|
|
400
|
+
|
|
401
|
+
type IndexRow = { name: string };
|
|
402
|
+
const indexes = nativeDb.prepare(`PRAGMA index_list(brain_page_edges)`).all() as IndexRow[];
|
|
403
|
+
const names = indexes.map((r) => r.name);
|
|
404
|
+
|
|
405
|
+
expect(names).toContain('idx_brain_edges_last_reinforced');
|
|
406
|
+
expect(names).toContain('idx_brain_edges_plasticity_class');
|
|
407
|
+
expect(names).toContain('idx_brain_edges_stability');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('M3-7: total column count is 12 (6 original + 6 new)', async () => {
|
|
411
|
+
await openDb();
|
|
412
|
+
const cols = await getTableColumns('brain_page_edges');
|
|
413
|
+
// Original 6: from_id, to_id, edge_type, weight, provenance, created_at
|
|
414
|
+
// New 6: last_reinforced_at, reinforcement_count, plasticity_class,
|
|
415
|
+
// last_depressed_at, depression_count, stability_score
|
|
416
|
+
expect(cols.size).toBe(12);
|
|
417
|
+
});
|
|
418
|
+
});
|