@cleocode/core 2026.4.11 → 2026.4.13
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/codebase-map/analyzers/architecture.d.ts.map +1 -1
- package/dist/codebase-map/analyzers/architecture.js +0 -1
- package/dist/codebase-map/analyzers/architecture.js.map +1 -1
- package/dist/conduit/local-transport.d.ts +18 -8
- package/dist/conduit/local-transport.d.ts.map +1 -1
- package/dist/conduit/local-transport.js +23 -13
- package/dist/conduit/local-transport.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.js +175 -68950
- package/dist/index.js.map +1 -7
- package/dist/init.d.ts +1 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1 -2
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +8 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +13 -6
- package/dist/internal.js.map +1 -1
- package/dist/memory/learnings.d.ts +2 -2
- package/dist/memory/patterns.d.ts +6 -6
- package/dist/output.d.ts +32 -11
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +67 -67
- package/dist/output.js.map +1 -1
- package/dist/paths.js +80 -14
- package/dist/paths.js.map +1 -1
- package/dist/skills/dynamic-skill-generator.d.ts +0 -2
- package/dist/skills/dynamic-skill-generator.d.ts.map +1 -1
- package/dist/skills/dynamic-skill-generator.js.map +1 -1
- package/dist/store/agent-registry-accessor.d.ts +203 -12
- package/dist/store/agent-registry-accessor.d.ts.map +1 -1
- package/dist/store/agent-registry-accessor.js +618 -100
- package/dist/store/agent-registry-accessor.js.map +1 -1
- package/dist/store/api-key-kdf.d.ts +73 -0
- package/dist/store/api-key-kdf.d.ts.map +1 -0
- package/dist/store/api-key-kdf.js +84 -0
- package/dist/store/api-key-kdf.js.map +1 -0
- package/dist/store/cleanup-legacy.js +171 -0
- package/dist/store/cleanup-legacy.js.map +1 -0
- package/dist/store/conduit-sqlite.d.ts +184 -0
- package/dist/store/conduit-sqlite.d.ts.map +1 -0
- package/dist/store/conduit-sqlite.js +570 -0
- package/dist/store/conduit-sqlite.js.map +1 -0
- package/dist/store/global-salt.d.ts +78 -0
- package/dist/store/global-salt.d.ts.map +1 -0
- package/dist/store/global-salt.js +147 -0
- package/dist/store/global-salt.js.map +1 -0
- package/dist/store/migrate-signaldock-to-conduit.d.ts +81 -0
- package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -0
- package/dist/store/migrate-signaldock-to-conduit.js +555 -0
- package/dist/store/migrate-signaldock-to-conduit.js.map +1 -0
- package/dist/store/nexus-sqlite.js +28 -3
- package/dist/store/nexus-sqlite.js.map +1 -1
- package/dist/store/signaldock-sqlite.d.ts +122 -19
- package/dist/store/signaldock-sqlite.d.ts.map +1 -1
- package/dist/store/signaldock-sqlite.js +401 -251
- package/dist/store/signaldock-sqlite.js.map +1 -1
- package/dist/store/sqlite-backup.js +122 -4
- package/dist/store/sqlite-backup.js.map +1 -1
- package/dist/system/backup.d.ts +0 -26
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/runtime.d.ts +0 -2
- package/dist/system/runtime.d.ts.map +1 -1
- package/dist/system/runtime.js +3 -3
- package/dist/system/runtime.js.map +1 -1
- package/dist/tasks/add.d.ts +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +98 -23
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +4 -1
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +4 -1
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/labels.d.ts.map +1 -1
- package/dist/tasks/labels.js +4 -1
- package/dist/tasks/labels.js.map +1 -1
- package/dist/tasks/relates.d.ts.map +1 -1
- package/dist/tasks/relates.js +16 -4
- package/dist/tasks/relates.js.map +1 -1
- package/dist/tasks/show.d.ts.map +1 -1
- package/dist/tasks/show.js +4 -1
- package/dist/tasks/show.js.map +1 -1
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +32 -6
- package/dist/tasks/update.js.map +1 -1
- package/dist/validation/engine.d.ts.map +1 -1
- package/dist/validation/engine.js +16 -4
- package/dist/validation/engine.js.map +1 -1
- package/dist/validation/param-utils.d.ts +5 -3
- package/dist/validation/param-utils.d.ts.map +1 -1
- package/dist/validation/param-utils.js +8 -6
- package/dist/validation/param-utils.js.map +1 -1
- package/dist/validation/protocols/_shared.d.ts.map +1 -1
- package/dist/validation/protocols/_shared.js +13 -6
- package/dist/validation/protocols/_shared.js.map +1 -1
- package/package.json +9 -7
- package/src/adapters/__tests__/manager.test.ts +0 -1
- package/src/codebase-map/analyzers/architecture.ts +0 -1
- package/src/conduit/__tests__/local-credential-flow.test.ts +20 -18
- package/src/conduit/__tests__/local-transport.test.ts +14 -12
- package/src/conduit/local-transport.ts +23 -13
- package/src/config.ts +0 -1
- package/src/errors.ts +24 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +2 -5
- package/src/init.ts +1 -2
- package/src/internal.ts +96 -2
- package/src/lifecycle/cant/lifecycle-rcasd.cant +133 -0
- package/src/memory/__tests__/engine-compat.test.ts +2 -2
- package/src/memory/__tests__/pipeline-manifest-sqlite.test.ts +4 -4
- package/src/observability/__tests__/index.test.ts +4 -4
- package/src/observability/__tests__/log-filter.test.ts +4 -4
- package/src/output.ts +73 -75
- package/src/sessions/__tests__/session-grade.integration.test.ts +1 -1
- package/src/sessions/__tests__/session-grade.test.ts +2 -2
- package/src/skills/__tests__/dynamic-skill-generator.test.ts +0 -2
- package/src/skills/dynamic-skill-generator.ts +0 -2
- package/src/store/__tests__/agent-registry-accessor.test.ts +807 -0
- package/src/store/__tests__/api-key-kdf.test.ts +113 -0
- package/src/store/__tests__/backup-crypto.test.ts +101 -0
- package/src/store/__tests__/backup-pack.test.ts +491 -0
- package/src/store/__tests__/backup-unpack.test.ts +298 -0
- package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
- package/src/store/__tests__/global-salt.test.ts +195 -0
- package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
- package/src/store/__tests__/regenerators.test.ts +234 -0
- package/src/store/__tests__/restore-conflict-report.test.ts +274 -0
- package/src/store/__tests__/restore-json-merge.test.ts +521 -0
- package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
- package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
- package/src/store/__tests__/sqlite-backup.test.ts +5 -1
- package/src/store/__tests__/t310-integration.test.ts +1150 -0
- package/src/store/__tests__/t310-readiness.test.ts +111 -0
- package/src/store/__tests__/t311-integration.test.ts +661 -0
- package/src/store/agent-registry-accessor.ts +847 -140
- package/src/store/api-key-kdf.ts +104 -0
- package/src/store/backup-crypto.ts +209 -0
- package/src/store/backup-pack.ts +739 -0
- package/src/store/backup-unpack.ts +583 -0
- package/src/store/conduit-sqlite.ts +655 -0
- package/src/store/global-salt.ts +175 -0
- package/src/store/migrate-signaldock-to-conduit.ts +669 -0
- package/src/store/regenerators.ts +243 -0
- package/src/store/restore-conflict-report.ts +317 -0
- package/src/store/restore-json-merge.ts +653 -0
- package/src/store/signaldock-sqlite.ts +431 -254
- package/src/store/sqlite-backup.ts +185 -10
- package/src/store/t310-readiness.ts +119 -0
- package/src/system/backup.ts +2 -62
- package/src/system/runtime.ts +4 -6
- package/src/tasks/__tests__/error-hints.test.ts +256 -0
- package/src/tasks/add.ts +99 -9
- package/src/tasks/complete.ts +4 -1
- package/src/tasks/find.ts +4 -1
- package/src/tasks/labels.ts +4 -1
- package/src/tasks/relates.ts +16 -4
- package/src/tasks/show.ts +4 -1
- package/src/tasks/update.ts +32 -3
- package/src/validation/__tests__/error-hints.test.ts +97 -0
- package/src/validation/engine.ts +16 -1
- package/src/validation/param-utils.ts +10 -7
- package/src/validation/protocols/_shared.ts +14 -6
- package/src/validation/protocols/cant/architecture-decision.cant +80 -0
- package/src/validation/protocols/cant/artifact-publish.cant +95 -0
- package/src/validation/protocols/cant/consensus.cant +74 -0
- package/src/validation/protocols/cant/contribution.cant +82 -0
- package/src/validation/protocols/cant/decomposition.cant +92 -0
- package/src/validation/protocols/cant/implementation.cant +67 -0
- package/src/validation/protocols/cant/provenance.cant +88 -0
- package/src/validation/protocols/cant/release.cant +96 -0
- package/src/validation/protocols/cant/research.cant +66 -0
- package/src/validation/protocols/cant/specification.cant +67 -0
- package/src/validation/protocols/cant/testing.cant +88 -0
- package/src/validation/protocols/cant/validation.cant +65 -0
- package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
- package/templates/config.template.json +0 -1
- package/templates/global-config.template.json +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for global-tier SQLite VACUUM INTO backup (vacuumIntoGlobalBackup,
|
|
3
|
-
* listGlobalSqliteBackups)
|
|
3
|
+
* listGlobalSqliteBackups) and global-salt raw-file backup (backupGlobalSalt,
|
|
4
|
+
* listGlobalSaltBackups).
|
|
4
5
|
*
|
|
5
6
|
* All tests use a tmp-dir override via `cleoHomeOverride` so they NEVER
|
|
6
7
|
* touch the real user's $XDG_DATA_HOME/cleo/ directory or corrupt actual
|
|
7
|
-
* nexus backups.
|
|
8
|
+
* nexus/signaldock backups.
|
|
8
9
|
*
|
|
9
10
|
* Coverage:
|
|
10
11
|
* - vacuumIntoGlobalBackup creates a snapshot and increments file count
|
|
@@ -14,12 +15,20 @@
|
|
|
14
15
|
* - Scope filter: listGlobalSqliteBackups with prefix excludes other prefixes
|
|
15
16
|
* - Restore round-trip: snapshot then restore preserves DB content
|
|
16
17
|
* - XDG path: snapshots land under cleoHomeOverride/backups/sqlite/, not hardcoded ~/.cleo/
|
|
18
|
+
* - TC-100: vacuumIntoBackupAll includes conduit.db snapshot
|
|
19
|
+
* - TC-101: vacuumIntoGlobalBackup('signaldock') writes snapshot to global backups dir
|
|
20
|
+
* - TC-102: backupGlobalSalt writes binary file with 0o600 permissions
|
|
21
|
+
* - TC-103: Rotation: 11th conduit snapshot deletes the oldest
|
|
22
|
+
* - TC-104: listSqliteBackupsAll returns conduit key in result map
|
|
23
|
+
* - TC-105: listGlobalSqliteBackups('signaldock') returns signaldock snapshots
|
|
17
24
|
*
|
|
18
25
|
* @task T306
|
|
26
|
+
* @task T369
|
|
19
27
|
* @epic T299
|
|
28
|
+
* @epic T310
|
|
20
29
|
*/
|
|
21
30
|
|
|
22
|
-
import { existsSync, mkdirSync, readdirSync, utimesSync, writeFileSync } from 'node:fs';
|
|
31
|
+
import { existsSync, mkdirSync, readdirSync, statSync, utimesSync, writeFileSync } from 'node:fs';
|
|
23
32
|
import { homedir, tmpdir } from 'node:os';
|
|
24
33
|
import { join } from 'node:path';
|
|
25
34
|
import { DatabaseSync } from 'node:sqlite';
|
|
@@ -278,4 +287,299 @@ describe('sqlite-backup global tier', () => {
|
|
|
278
287
|
snapDb.close();
|
|
279
288
|
}
|
|
280
289
|
});
|
|
290
|
+
|
|
291
|
+
// ==========================================================================
|
|
292
|
+
// TC-100 through TC-105: T369 — conduit, signaldock, global-salt backup
|
|
293
|
+
// ==========================================================================
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* TC-100: vacuumIntoBackupAll includes conduit.db snapshot in
|
|
297
|
+
* `.cleo/backups/sqlite/` alongside tasks and brain.
|
|
298
|
+
*
|
|
299
|
+
* @task T369
|
|
300
|
+
* @epic T310
|
|
301
|
+
*/
|
|
302
|
+
it('TC-100: vacuumIntoBackupAll includes conduit.db snapshot', async () => {
|
|
303
|
+
vi.resetModules();
|
|
304
|
+
const cwd = join(tmpdir(), `cleo-tc100-${Date.now()}`);
|
|
305
|
+
const cleoDir = join(cwd, '.cleo');
|
|
306
|
+
const tasksDbPath = join(cleoDir, 'tasks.db');
|
|
307
|
+
const brainDbPath = join(cleoDir, 'brain.db');
|
|
308
|
+
const conduitDbPath = join(cleoDir, 'conduit.db');
|
|
309
|
+
mkdirSync(cleoDir, { recursive: true });
|
|
310
|
+
|
|
311
|
+
// Seed all three databases
|
|
312
|
+
for (const dbPath of [tasksDbPath, brainDbPath, conduitDbPath]) {
|
|
313
|
+
const db = new DatabaseSync(dbPath);
|
|
314
|
+
db.exec(
|
|
315
|
+
"CREATE TABLE IF NOT EXISTS test_data (id INTEGER PRIMARY KEY, value TEXT); INSERT INTO test_data (value) VALUES ('hello');",
|
|
316
|
+
);
|
|
317
|
+
db.close();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const tasksDb = new DatabaseSync(tasksDbPath);
|
|
321
|
+
const brainDb = new DatabaseSync(brainDbPath);
|
|
322
|
+
const conduitDb = new DatabaseSync(conduitDbPath);
|
|
323
|
+
|
|
324
|
+
vi.doMock('../sqlite.js', () => ({ getNativeDb: () => tasksDb, getDb: () => tasksDb }));
|
|
325
|
+
vi.doMock('../brain-sqlite.js', () => ({ getBrainNativeDb: () => brainDb }));
|
|
326
|
+
vi.doMock('../conduit-sqlite.js', () => ({ getConduitNativeDb: () => conduitDb }));
|
|
327
|
+
vi.doMock('../nexus-sqlite.js', () => ({ getNexusNativeDb: () => null }));
|
|
328
|
+
vi.doMock('../signaldock-sqlite.js', () => ({
|
|
329
|
+
getGlobalSignaldockNativeDb: () => null,
|
|
330
|
+
getGlobalSignaldockDbPath: () => '',
|
|
331
|
+
}));
|
|
332
|
+
vi.doMock('../global-salt.js', () => ({ getGlobalSaltPath: () => join(cwd, 'global-salt') }));
|
|
333
|
+
vi.doMock('../../paths.js', () => ({
|
|
334
|
+
getCleoDir: () => cleoDir,
|
|
335
|
+
getCleoHome: () => cwd,
|
|
336
|
+
}));
|
|
337
|
+
|
|
338
|
+
const { vacuumIntoBackupAll, listSqliteBackupsAll } = await import('../sqlite-backup.js');
|
|
339
|
+
await vacuumIntoBackupAll({ cwd, force: true });
|
|
340
|
+
|
|
341
|
+
tasksDb.close();
|
|
342
|
+
brainDb.close();
|
|
343
|
+
conduitDb.close();
|
|
344
|
+
|
|
345
|
+
const allBackups = listSqliteBackupsAll(cwd);
|
|
346
|
+
|
|
347
|
+
// All three prefixes must be present
|
|
348
|
+
expect(allBackups).toHaveProperty('tasks');
|
|
349
|
+
expect(allBackups).toHaveProperty('brain');
|
|
350
|
+
expect(allBackups).toHaveProperty('conduit');
|
|
351
|
+
|
|
352
|
+
// Each must have at least one snapshot
|
|
353
|
+
expect(allBackups['tasks']?.length).toBeGreaterThanOrEqual(1);
|
|
354
|
+
expect(allBackups['brain']?.length).toBeGreaterThanOrEqual(1);
|
|
355
|
+
expect(allBackups['conduit']?.length).toBeGreaterThanOrEqual(1);
|
|
356
|
+
|
|
357
|
+
// Snapshot filenames must match the conduit- prefix
|
|
358
|
+
const conduitSnap = allBackups['conduit']?.[0];
|
|
359
|
+
expect(conduitSnap?.name).toMatch(/^conduit-\d{8}-\d{6}\.db$/);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* TC-101: vacuumIntoGlobalBackup('signaldock') writes a snapshot to the
|
|
364
|
+
* global backups directory at `cleoHomeOverride/backups/sqlite/`.
|
|
365
|
+
*
|
|
366
|
+
* @task T369
|
|
367
|
+
* @epic T310
|
|
368
|
+
*/
|
|
369
|
+
it('TC-101: vacuumIntoGlobalBackup(signaldock) writes snapshot to global backups dir', async () => {
|
|
370
|
+
vi.resetModules();
|
|
371
|
+
const cleoHome = join(tmpdir(), `cleo-tc101-${Date.now()}`);
|
|
372
|
+
const sdDbPath = join(cleoHome, 'signaldock.db');
|
|
373
|
+
mkdirSync(cleoHome, { recursive: true });
|
|
374
|
+
|
|
375
|
+
const db = new DatabaseSync(sdDbPath);
|
|
376
|
+
db.exec(
|
|
377
|
+
"CREATE TABLE IF NOT EXISTS agents (id TEXT PRIMARY KEY); INSERT INTO agents VALUES ('agent-1');",
|
|
378
|
+
);
|
|
379
|
+
db.close();
|
|
380
|
+
|
|
381
|
+
const sdDb = new DatabaseSync(sdDbPath);
|
|
382
|
+
vi.doMock('../signaldock-sqlite.js', () => ({
|
|
383
|
+
getGlobalSignaldockNativeDb: () => sdDb,
|
|
384
|
+
getGlobalSignaldockDbPath: () => sdDbPath,
|
|
385
|
+
}));
|
|
386
|
+
vi.doMock('../nexus-sqlite.js', () => ({ getNexusNativeDb: () => null }));
|
|
387
|
+
vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
|
|
388
|
+
|
|
389
|
+
const { vacuumIntoGlobalBackup } = await import('../sqlite-backup.js');
|
|
390
|
+
const result = await vacuumIntoGlobalBackup('signaldock', { cleoHomeOverride: cleoHome });
|
|
391
|
+
|
|
392
|
+
sdDb.close();
|
|
393
|
+
|
|
394
|
+
expect(result.snapshotPath).toBeTruthy();
|
|
395
|
+
expect(result.snapshotPath).toContain('signaldock-');
|
|
396
|
+
expect(result.snapshotPath).toContain(join(cleoHome, 'backups', 'sqlite'));
|
|
397
|
+
expect(existsSync(result.snapshotPath)).toBe(true);
|
|
398
|
+
|
|
399
|
+
// Snapshot must pass integrity_check
|
|
400
|
+
const snap = new DatabaseSync(result.snapshotPath, { readonly: true });
|
|
401
|
+
try {
|
|
402
|
+
const row = snap.prepare('PRAGMA integrity_check').get() as Record<string, unknown>;
|
|
403
|
+
const ok = row?.['integrity_check'] ?? row?.['integrity check'];
|
|
404
|
+
expect(ok).toBe('ok');
|
|
405
|
+
} finally {
|
|
406
|
+
snap.close();
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* TC-102: backupGlobalSalt writes a 32-byte binary file to
|
|
412
|
+
* `cleoHomeOverride/backups/global-salt-<ts>` with 0o600 permissions.
|
|
413
|
+
*
|
|
414
|
+
* @task T369
|
|
415
|
+
* @epic T310
|
|
416
|
+
*/
|
|
417
|
+
it('TC-102: backupGlobalSalt writes binary file with 0o600 permissions', async () => {
|
|
418
|
+
vi.resetModules();
|
|
419
|
+
const cleoHome = join(tmpdir(), `cleo-tc102-${Date.now()}`);
|
|
420
|
+
mkdirSync(cleoHome, { recursive: true });
|
|
421
|
+
|
|
422
|
+
// Write a fake 32-byte global-salt file at cleoHomeOverride/global-salt
|
|
423
|
+
const saltPath = join(cleoHome, 'global-salt');
|
|
424
|
+
const saltBytes = Buffer.alloc(32, 0xab);
|
|
425
|
+
writeFileSync(saltPath, saltBytes, { mode: 0o600 });
|
|
426
|
+
|
|
427
|
+
vi.doMock('../global-salt.js', () => ({ getGlobalSaltPath: () => saltPath }));
|
|
428
|
+
vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
|
|
429
|
+
|
|
430
|
+
const { backupGlobalSalt } = await import('../sqlite-backup.js');
|
|
431
|
+
const result = await backupGlobalSalt({ cleoHomeOverride: cleoHome });
|
|
432
|
+
|
|
433
|
+
expect(result.snapshotPath).toBeTruthy();
|
|
434
|
+
expect(result.snapshotPath).toContain('global-salt-');
|
|
435
|
+
expect(existsSync(result.snapshotPath)).toBe(true);
|
|
436
|
+
|
|
437
|
+
// Must be 32 bytes
|
|
438
|
+
const s = statSync(result.snapshotPath);
|
|
439
|
+
expect(s.size).toBe(32);
|
|
440
|
+
|
|
441
|
+
// Must have 0o600 permissions (owner read/write only)
|
|
442
|
+
const mode = s.mode & 0o777;
|
|
443
|
+
expect(mode).toBe(0o600);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* TC-103: Rotation — 11th conduit snapshot deletes the oldest so no more
|
|
448
|
+
* than 10 conduit snapshots remain in `.cleo/backups/sqlite/`.
|
|
449
|
+
*
|
|
450
|
+
* @task T369
|
|
451
|
+
* @epic T310
|
|
452
|
+
*/
|
|
453
|
+
it('TC-103: rotation keeps max 10 conduit snapshots, deletes the oldest', async () => {
|
|
454
|
+
vi.resetModules();
|
|
455
|
+
const cwd = join(tmpdir(), `cleo-tc103-${Date.now()}`);
|
|
456
|
+
const cleoDir = join(cwd, '.cleo');
|
|
457
|
+
const conduitDbPath = join(cleoDir, 'conduit.db');
|
|
458
|
+
const backupDir = join(cleoDir, 'backups', 'sqlite');
|
|
459
|
+
mkdirSync(backupDir, { recursive: true });
|
|
460
|
+
|
|
461
|
+
// Seed the conduit DB
|
|
462
|
+
const dbInit = new DatabaseSync(conduitDbPath);
|
|
463
|
+
dbInit.exec('CREATE TABLE IF NOT EXISTS msgs (id INTEGER PRIMARY KEY);');
|
|
464
|
+
dbInit.close();
|
|
465
|
+
|
|
466
|
+
// Pre-create 11 fake conduit snapshot files with ascending mtimes
|
|
467
|
+
const staleFiles: string[] = [];
|
|
468
|
+
for (let i = 0; i < 11; i++) {
|
|
469
|
+
const day = String(i + 1).padStart(2, '0');
|
|
470
|
+
const name = `conduit-202601${day}-120000.db`;
|
|
471
|
+
const p = join(backupDir, name);
|
|
472
|
+
writeFileSync(p, 'fake-conduit-snapshot');
|
|
473
|
+
utimesSync(p, 1_700_000_000 + i * 100, 1_700_000_000 + i * 100);
|
|
474
|
+
staleFiles.push(p);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const conduitDb = new DatabaseSync(conduitDbPath);
|
|
478
|
+
vi.doMock('../conduit-sqlite.js', () => ({ getConduitNativeDb: () => conduitDb }));
|
|
479
|
+
vi.doMock('../sqlite.js', () => ({ getNativeDb: () => null, getDb: () => null }));
|
|
480
|
+
vi.doMock('../brain-sqlite.js', () => ({ getBrainNativeDb: () => null }));
|
|
481
|
+
vi.doMock('../nexus-sqlite.js', () => ({ getNexusNativeDb: () => null }));
|
|
482
|
+
vi.doMock('../signaldock-sqlite.js', () => ({
|
|
483
|
+
getGlobalSignaldockNativeDb: () => null,
|
|
484
|
+
getGlobalSignaldockDbPath: () => '',
|
|
485
|
+
}));
|
|
486
|
+
vi.doMock('../global-salt.js', () => ({ getGlobalSaltPath: () => '' }));
|
|
487
|
+
vi.doMock('../../paths.js', () => ({
|
|
488
|
+
getCleoDir: () => cleoDir,
|
|
489
|
+
getCleoHome: () => cwd,
|
|
490
|
+
}));
|
|
491
|
+
|
|
492
|
+
const { vacuumIntoBackupAll } = await import('../sqlite-backup.js');
|
|
493
|
+
await vacuumIntoBackupAll({ cwd, force: true });
|
|
494
|
+
|
|
495
|
+
conduitDb.close();
|
|
496
|
+
|
|
497
|
+
const remaining = readdirSync(backupDir).filter((f) => /^conduit-\d{8}-\d{6}\.db$/.test(f));
|
|
498
|
+
// After adding the 12th (11 stale + 1 new), rotation must trim to ≤10
|
|
499
|
+
expect(remaining.length).toBeLessThanOrEqual(10);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* TC-104: listSqliteBackupsAll returns a `conduit` key in its result map.
|
|
504
|
+
*
|
|
505
|
+
* @task T369
|
|
506
|
+
* @epic T310
|
|
507
|
+
*/
|
|
508
|
+
it('TC-104: listSqliteBackupsAll returns conduit key in result map', async () => {
|
|
509
|
+
vi.resetModules();
|
|
510
|
+
const cwd = join(tmpdir(), `cleo-tc104-${Date.now()}`);
|
|
511
|
+
const cleoDir = join(cwd, '.cleo');
|
|
512
|
+
const backupDir = join(cleoDir, 'backups', 'sqlite');
|
|
513
|
+
mkdirSync(backupDir, { recursive: true });
|
|
514
|
+
|
|
515
|
+
// Write a fake conduit snapshot
|
|
516
|
+
writeFileSync(join(backupDir, 'conduit-20260101-120000.db'), 'fake-conduit');
|
|
517
|
+
|
|
518
|
+
vi.doMock('../sqlite.js', () => ({ getNativeDb: () => null, getDb: () => null }));
|
|
519
|
+
vi.doMock('../brain-sqlite.js', () => ({ getBrainNativeDb: () => null }));
|
|
520
|
+
vi.doMock('../conduit-sqlite.js', () => ({ getConduitNativeDb: () => null }));
|
|
521
|
+
vi.doMock('../nexus-sqlite.js', () => ({ getNexusNativeDb: () => null }));
|
|
522
|
+
vi.doMock('../signaldock-sqlite.js', () => ({
|
|
523
|
+
getGlobalSignaldockNativeDb: () => null,
|
|
524
|
+
getGlobalSignaldockDbPath: () => '',
|
|
525
|
+
}));
|
|
526
|
+
vi.doMock('../global-salt.js', () => ({ getGlobalSaltPath: () => '' }));
|
|
527
|
+
vi.doMock('../../paths.js', () => ({
|
|
528
|
+
getCleoDir: () => cleoDir,
|
|
529
|
+
getCleoHome: () => cwd,
|
|
530
|
+
}));
|
|
531
|
+
|
|
532
|
+
const { listSqliteBackupsAll } = await import('../sqlite-backup.js');
|
|
533
|
+
const all = listSqliteBackupsAll(cwd);
|
|
534
|
+
|
|
535
|
+
// The result map must include all three registered prefixes
|
|
536
|
+
expect(all).toHaveProperty('tasks');
|
|
537
|
+
expect(all).toHaveProperty('brain');
|
|
538
|
+
expect(all).toHaveProperty('conduit');
|
|
539
|
+
|
|
540
|
+
// The conduit entry should include the seeded fake file
|
|
541
|
+
expect(all['conduit']?.length).toBeGreaterThanOrEqual(1);
|
|
542
|
+
expect(all['conduit']?.[0]?.name).toBe('conduit-20260101-120000.db');
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* TC-105: listGlobalSqliteBackups('signaldock') returns only the global
|
|
547
|
+
* signaldock snapshots, not nexus ones.
|
|
548
|
+
*
|
|
549
|
+
* @task T369
|
|
550
|
+
* @epic T310
|
|
551
|
+
*/
|
|
552
|
+
it('TC-105: listGlobalSqliteBackups(signaldock) returns only signaldock entries', async () => {
|
|
553
|
+
vi.resetModules();
|
|
554
|
+
const cleoHome = join(tmpdir(), `cleo-tc105-${Date.now()}`);
|
|
555
|
+
const backupDir = join(cleoHome, 'backups', 'sqlite');
|
|
556
|
+
mkdirSync(backupDir, { recursive: true });
|
|
557
|
+
|
|
558
|
+
vi.doMock('../../paths.js', () => ({ getCleoHome: () => cleoHome }));
|
|
559
|
+
|
|
560
|
+
// Write two signaldock and one nexus snapshot
|
|
561
|
+
writeFileSync(join(backupDir, 'signaldock-20260101-120000.db'), 'sd-1');
|
|
562
|
+
writeFileSync(join(backupDir, 'signaldock-20260102-120000.db'), 'sd-2');
|
|
563
|
+
writeFileSync(join(backupDir, 'nexus-20260101-120000.db'), 'nexus-1');
|
|
564
|
+
|
|
565
|
+
const { listGlobalSqliteBackups } = await import('../sqlite-backup.js');
|
|
566
|
+
|
|
567
|
+
const sdEntries = listGlobalSqliteBackups('signaldock', cleoHome);
|
|
568
|
+
const nexusEntries = listGlobalSqliteBackups('nexus', cleoHome);
|
|
569
|
+
|
|
570
|
+
// signaldock filter must return exactly the two signaldock files
|
|
571
|
+
expect(sdEntries.length).toBe(2);
|
|
572
|
+
expect(sdEntries.every((e) => e.name.startsWith('signaldock-'))).toBe(true);
|
|
573
|
+
|
|
574
|
+
// nexus filter must return exactly the one nexus file
|
|
575
|
+
expect(nexusEntries.length).toBe(1);
|
|
576
|
+
expect(nexusEntries[0]?.name).toBe('nexus-20260101-120000.db');
|
|
577
|
+
|
|
578
|
+
// Each entry has required fields
|
|
579
|
+
for (const e of sdEntries) {
|
|
580
|
+
expect(e.path).toBeTruthy();
|
|
581
|
+
expect(typeof e.size).toBe('number');
|
|
582
|
+
expect(e.mtime).toBeInstanceOf(Date);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
281
585
|
});
|
|
@@ -153,6 +153,7 @@ describe('sqlite-backup', () => {
|
|
|
153
153
|
it('listSqliteBackups and listBrainBackups return prefix-specific entries newest-first', async () => {
|
|
154
154
|
vi.doMock('../sqlite.js', () => ({ getNativeDb: () => null }));
|
|
155
155
|
vi.doMock('../brain-sqlite.js', () => ({ getBrainNativeDb: () => null }));
|
|
156
|
+
vi.doMock('../conduit-sqlite.js', () => ({ getConduitNativeDb: () => null }));
|
|
156
157
|
const tempDir = join(tmpdir(), `cleo-test-list-${Date.now()}`);
|
|
157
158
|
const backupDir = join(tempDir, 'backups', 'sqlite');
|
|
158
159
|
mkdirSync(backupDir, { recursive: true });
|
|
@@ -186,8 +187,11 @@ describe('sqlite-backup', () => {
|
|
|
186
187
|
'tasks-20260101-120000.db',
|
|
187
188
|
]);
|
|
188
189
|
expect(brainList.map((e) => e.name)).toEqual(['brain-20260103-120000.db']);
|
|
189
|
-
|
|
190
|
+
// conduit is now a registered prefix (T369); no conduit files exist in
|
|
191
|
+
// this temp dir so its bucket is empty but still present in the map.
|
|
192
|
+
expect(Object.keys(all).sort()).toEqual(['brain', 'conduit', 'tasks']);
|
|
190
193
|
expect(all['tasks']?.length).toBe(2);
|
|
191
194
|
expect(all['brain']?.length).toBe(1);
|
|
195
|
+
expect(all['conduit']?.length).toBe(0);
|
|
192
196
|
});
|
|
193
197
|
});
|