@cleocode/core 2026.4.68 → 2026.4.69

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/core",
3
- "version": "2026.4.68",
3
+ "version": "2026.4.69",
4
4
  "description": "CLEO core business logic kernel — tasks, sessions, memory, orchestration, lifecycle, with bundled SQLite store",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -76,13 +76,13 @@
76
76
  "write-file-atomic": "^7.0.1",
77
77
  "yaml": "^2.8.3",
78
78
  "zod": "^4.3.6",
79
- "@cleocode/agents": "2026.4.68",
80
- "@cleocode/caamp": "2026.4.68",
81
- "@cleocode/contracts": "2026.4.68",
82
- "@cleocode/adapters": "2026.4.68",
83
- "@cleocode/lafs": "2026.4.68",
84
- "@cleocode/nexus": "2026.4.68",
85
- "@cleocode/skills": "2026.4.68"
79
+ "@cleocode/adapters": "2026.4.69",
80
+ "@cleocode/agents": "2026.4.69",
81
+ "@cleocode/caamp": "2026.4.69",
82
+ "@cleocode/contracts": "2026.4.69",
83
+ "@cleocode/lafs": "2026.4.69",
84
+ "@cleocode/nexus": "2026.4.69",
85
+ "@cleocode/skills": "2026.4.69"
86
86
  },
87
87
  "engines": {
88
88
  "node": ">=24.0.0"
@@ -0,0 +1,206 @@
1
+ /**
2
+ * T759 regression tests: brain_observations provenance column hotfix.
3
+ *
4
+ * Root cause: packages/cleo/migrations/drizzle-brain/ only shipped the initial
5
+ * migration. On a fresh install, brain_page_edges lacked the `provenance` column
6
+ * (added by T528). The T626 post-migration guard ran an UPDATE using
7
+ * `WHERE provenance LIKE ...` which threw "no such column: provenance".
8
+ * That error propagated through observeBrain → memoryObserve and surfaced as
9
+ * E_BRAIN_OBSERVE: no such column: provenance.
10
+ *
11
+ * Fix:
12
+ * 1. All brain migrations are now synced to packages/cleo/migrations/drizzle-brain/
13
+ * by the build.mjs syncMigrationsToCleoPackage() step.
14
+ * 2. brain-sqlite.ts T626 guard now calls ensureColumns for `provenance` on
15
+ * brain_page_edges before running the UPDATE, so the guard is safe even if
16
+ * T528 migration somehow hasn't run yet.
17
+ *
18
+ * Test plan:
19
+ * OBS-1: observeBrain succeeds on a fresh brain.db (all migrations run)
20
+ * OBS-2: brain_observations has agent + quality_score + memory_tier columns
21
+ * OBS-3: brain_page_edges has provenance column after DB init
22
+ * OBS-4: The T626 guard UPDATE runs without error (provenance column present)
23
+ * OBS-5: session.end memory-bridge write does not throw provenance error
24
+ * OBS-6: Simulate pre-T528 brain.db state: ensureColumns adds provenance
25
+ * and T626 guard runs without error
26
+ *
27
+ * @task T759
28
+ * @epic T569
29
+ */
30
+
31
+ import { mkdtemp, rm } from 'node:fs/promises';
32
+ import { tmpdir } from 'node:os';
33
+ import { join } from 'node:path';
34
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
35
+
36
+ vi.setConfig({ testTimeout: 30_000 });
37
+
38
+ import { vi } from 'vitest';
39
+
40
+ let tempDir: string;
41
+
42
+ beforeEach(async () => {
43
+ tempDir = await mkdtemp(join(tmpdir(), 'cleo-t759-'));
44
+ const cleoDir = join(tempDir, '.cleo');
45
+ const { mkdirSync } = await import('node:fs');
46
+ mkdirSync(cleoDir, { recursive: true });
47
+ process.env['CLEO_DIR'] = cleoDir;
48
+ });
49
+
50
+ afterEach(async () => {
51
+ const { closeBrainDb } = await import('../../store/brain-sqlite.js');
52
+ closeBrainDb();
53
+ const { resetBrainDbState } = await import('../../store/brain-sqlite.js');
54
+ resetBrainDbState();
55
+ delete process.env['CLEO_DIR'];
56
+ await rm(tempDir, { recursive: true, force: true });
57
+ });
58
+
59
+ async function getTableColumns(tableName: string): Promise<Set<string>> {
60
+ const { getBrainNativeDb } = await import('../../store/brain-sqlite.js');
61
+ const nativeDb = getBrainNativeDb();
62
+ if (!nativeDb) throw new Error('nativeDb is null after getBrainDb()');
63
+ type PragmaRow = { name: string };
64
+ const rows = nativeDb.prepare(`PRAGMA table_info(${tableName})`).all() as PragmaRow[];
65
+ return new Set(rows.map((r) => r.name));
66
+ }
67
+
68
+ describe('T759: brain_observations provenance hotfix', () => {
69
+ describe('OBS-1: observeBrain succeeds on fresh brain.db', () => {
70
+ it('should store an observation without error', async () => {
71
+ const { observeBrain } = await import('../brain-retrieval.js');
72
+ const result = await observeBrain(tempDir, {
73
+ text: 'T759 regression test observation',
74
+ title: 'T759 test',
75
+ sourceType: 'manual',
76
+ });
77
+ expect(result).toBeDefined();
78
+ expect(result.id).toMatch(/^O-/);
79
+ expect(result.type).toBe('discovery');
80
+ expect(result.createdAt).toBeTruthy();
81
+ });
82
+ });
83
+
84
+ describe('OBS-2: brain_observations has all required columns after migration', () => {
85
+ it('should have agent, quality_score, memory_tier, source_confidence, citation_count', async () => {
86
+ const { getBrainDb } = await import('../../store/brain-sqlite.js');
87
+ await getBrainDb(tempDir);
88
+ const cols = await getTableColumns('brain_observations');
89
+ // T417 columns
90
+ expect(cols.has('agent'), 'agent column missing (T417)').toBe(true);
91
+ // T531 columns
92
+ expect(cols.has('quality_score'), 'quality_score column missing (T531)').toBe(true);
93
+ // T549 columns
94
+ expect(cols.has('memory_tier'), 'memory_tier column missing (T549)').toBe(true);
95
+ expect(cols.has('memory_type'), 'memory_type column missing (T549)').toBe(true);
96
+ expect(cols.has('verified'), 'verified column missing (T549)').toBe(true);
97
+ expect(cols.has('source_confidence'), 'source_confidence column missing (T549)').toBe(true);
98
+ expect(cols.has('citation_count'), 'citation_count column missing (T549)').toBe(true);
99
+ // T726 columns
100
+ expect(cols.has('tier_promoted_at'), 'tier_promoted_at column missing (T726)').toBe(true);
101
+ expect(cols.has('tier_promotion_reason'), 'tier_promotion_reason column missing (T726)').toBe(
102
+ true,
103
+ );
104
+ // provenance MUST NOT appear — it is on brain_page_edges, not brain_observations
105
+ expect(cols.has('provenance'), 'provenance should NOT be on brain_observations').toBe(false);
106
+ });
107
+ });
108
+
109
+ describe('OBS-3: brain_page_edges has provenance column after DB init', () => {
110
+ it('should have provenance column on brain_page_edges (added by T528 migration)', async () => {
111
+ const { getBrainDb } = await import('../../store/brain-sqlite.js');
112
+ await getBrainDb(tempDir);
113
+ const cols = await getTableColumns('brain_page_edges');
114
+ expect(cols.has('provenance'), 'provenance column missing from brain_page_edges').toBe(true);
115
+ });
116
+ });
117
+
118
+ describe('OBS-4: T626 guard UPDATE runs without error', () => {
119
+ it('should not throw when running the co_retrieved normalization UPDATE', async () => {
120
+ const { getBrainDb, getBrainNativeDb } = await import('../../store/brain-sqlite.js');
121
+ await getBrainDb(tempDir);
122
+ const nativeDb = getBrainNativeDb();
123
+ expect(nativeDb, 'nativeDb should be set after getBrainDb()').not.toBeNull();
124
+ // The T626 guard UPDATE should have already run successfully during getBrainDb().
125
+ // Verify it can run again without error (idempotent).
126
+ expect(() => {
127
+ nativeDb!
128
+ .prepare(
129
+ `UPDATE brain_page_edges
130
+ SET edge_type = 'co_retrieved'
131
+ WHERE edge_type = 'relates_to'
132
+ AND provenance LIKE 'consolidation:%'`,
133
+ )
134
+ .run();
135
+ }).not.toThrow();
136
+ });
137
+ });
138
+
139
+ describe('OBS-5: memory bridge generation does not throw provenance error', () => {
140
+ it('should generate memory bridge content without E_BRAIN_OBSERVE', async () => {
141
+ // First write an observation so the bridge has content to read
142
+ const { observeBrain } = await import('../brain-retrieval.js');
143
+ await observeBrain(tempDir, {
144
+ text: 'Memory bridge test observation for T759',
145
+ title: 'T759 bridge test',
146
+ sourceType: 'manual',
147
+ });
148
+
149
+ // Generating the memory bridge triggers queryRecentObservations which reads
150
+ // brain_observations. This should not fail with "no such column: provenance".
151
+ const { writeMemoryBridge } = await import('../memory-bridge.js');
152
+ const result = await writeMemoryBridge(tempDir);
153
+ expect(result, 'writeMemoryBridge should return a result object').toBeDefined();
154
+ expect(result.path, 'result should have a path').toBeTruthy();
155
+ });
156
+ });
157
+
158
+ describe('OBS-6: ensureColumns adds provenance to brain_page_edges when missing', () => {
159
+ it('should add provenance column via ensureColumns and allow T626 guard UPDATE', async () => {
160
+ const { DatabaseSync } = await import('node:sqlite');
161
+
162
+ // Build an in-memory brain_page_edges table WITHOUT provenance (pre-T528 state)
163
+ const db = new DatabaseSync(':memory:');
164
+ db.exec(`
165
+ CREATE TABLE brain_page_edges (
166
+ from_id text NOT NULL, to_id text NOT NULL, edge_type text NOT NULL,
167
+ weight real DEFAULT 1, created_at text DEFAULT (datetime('now')) NOT NULL,
168
+ CONSTRAINT brain_page_edges_pk PRIMARY KEY(from_id, to_id, edge_type)
169
+ );
170
+ `);
171
+
172
+ // Confirm provenance is absent
173
+ type PragmaRow = { name: string };
174
+ const colsBefore = db.prepare('PRAGMA table_info(brain_page_edges)').all() as PragmaRow[];
175
+ expect(
176
+ colsBefore.some((c) => c.name === 'provenance'),
177
+ 'provenance should be absent before ensureColumns',
178
+ ).toBe(false);
179
+
180
+ // ensureColumns should add provenance without error
181
+ const { ensureColumns } = await import('../../store/migration-manager.js');
182
+ expect(() => {
183
+ ensureColumns(db, 'brain_page_edges', [{ name: 'provenance', ddl: 'text' }], 'brain');
184
+ }).not.toThrow();
185
+
186
+ // Confirm provenance is now present
187
+ const colsAfter = db.prepare('PRAGMA table_info(brain_page_edges)').all() as PragmaRow[];
188
+ expect(
189
+ colsAfter.some((c) => c.name === 'provenance'),
190
+ 'provenance should exist after ensureColumns',
191
+ ).toBe(true);
192
+
193
+ // T626 guard UPDATE must not throw
194
+ expect(() => {
195
+ db.prepare(
196
+ `UPDATE brain_page_edges
197
+ SET edge_type = 'co_retrieved'
198
+ WHERE edge_type = 'relates_to'
199
+ AND provenance LIKE 'consolidation:%'`,
200
+ ).run();
201
+ }).not.toThrow();
202
+
203
+ db.close();
204
+ });
205
+ });
206
+ });