@cleocode/core 2026.4.11 → 2026.4.12

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.
Files changed (169) hide show
  1. package/dist/codebase-map/analyzers/architecture.d.ts.map +1 -1
  2. package/dist/codebase-map/analyzers/architecture.js +0 -1
  3. package/dist/codebase-map/analyzers/architecture.js.map +1 -1
  4. package/dist/conduit/local-transport.d.ts +18 -8
  5. package/dist/conduit/local-transport.d.ts.map +1 -1
  6. package/dist/conduit/local-transport.js +23 -13
  7. package/dist/conduit/local-transport.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +0 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/errors.d.ts +19 -0
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +6 -0
  14. package/dist/errors.js.map +1 -1
  15. package/dist/index.js +175 -68950
  16. package/dist/index.js.map +1 -7
  17. package/dist/init.d.ts +1 -2
  18. package/dist/init.d.ts.map +1 -1
  19. package/dist/init.js +1 -2
  20. package/dist/init.js.map +1 -1
  21. package/dist/internal.d.ts +8 -3
  22. package/dist/internal.d.ts.map +1 -1
  23. package/dist/internal.js +13 -6
  24. package/dist/internal.js.map +1 -1
  25. package/dist/memory/learnings.d.ts +2 -2
  26. package/dist/memory/patterns.d.ts +6 -6
  27. package/dist/output.d.ts +32 -11
  28. package/dist/output.d.ts.map +1 -1
  29. package/dist/output.js +67 -67
  30. package/dist/output.js.map +1 -1
  31. package/dist/paths.js +80 -14
  32. package/dist/paths.js.map +1 -1
  33. package/dist/skills/dynamic-skill-generator.d.ts +0 -2
  34. package/dist/skills/dynamic-skill-generator.d.ts.map +1 -1
  35. package/dist/skills/dynamic-skill-generator.js.map +1 -1
  36. package/dist/store/agent-registry-accessor.d.ts +203 -12
  37. package/dist/store/agent-registry-accessor.d.ts.map +1 -1
  38. package/dist/store/agent-registry-accessor.js +618 -100
  39. package/dist/store/agent-registry-accessor.js.map +1 -1
  40. package/dist/store/api-key-kdf.d.ts +73 -0
  41. package/dist/store/api-key-kdf.d.ts.map +1 -0
  42. package/dist/store/api-key-kdf.js +84 -0
  43. package/dist/store/api-key-kdf.js.map +1 -0
  44. package/dist/store/cleanup-legacy.js +171 -0
  45. package/dist/store/cleanup-legacy.js.map +1 -0
  46. package/dist/store/conduit-sqlite.d.ts +184 -0
  47. package/dist/store/conduit-sqlite.d.ts.map +1 -0
  48. package/dist/store/conduit-sqlite.js +570 -0
  49. package/dist/store/conduit-sqlite.js.map +1 -0
  50. package/dist/store/global-salt.d.ts +78 -0
  51. package/dist/store/global-salt.d.ts.map +1 -0
  52. package/dist/store/global-salt.js +147 -0
  53. package/dist/store/global-salt.js.map +1 -0
  54. package/dist/store/migrate-signaldock-to-conduit.d.ts +81 -0
  55. package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -0
  56. package/dist/store/migrate-signaldock-to-conduit.js +555 -0
  57. package/dist/store/migrate-signaldock-to-conduit.js.map +1 -0
  58. package/dist/store/nexus-sqlite.js +28 -3
  59. package/dist/store/nexus-sqlite.js.map +1 -1
  60. package/dist/store/signaldock-sqlite.d.ts +122 -19
  61. package/dist/store/signaldock-sqlite.d.ts.map +1 -1
  62. package/dist/store/signaldock-sqlite.js +401 -251
  63. package/dist/store/signaldock-sqlite.js.map +1 -1
  64. package/dist/store/sqlite-backup.js +122 -4
  65. package/dist/store/sqlite-backup.js.map +1 -1
  66. package/dist/system/backup.d.ts +0 -26
  67. package/dist/system/backup.d.ts.map +1 -1
  68. package/dist/system/runtime.d.ts +0 -2
  69. package/dist/system/runtime.d.ts.map +1 -1
  70. package/dist/system/runtime.js +3 -3
  71. package/dist/system/runtime.js.map +1 -1
  72. package/dist/tasks/add.d.ts +1 -1
  73. package/dist/tasks/add.d.ts.map +1 -1
  74. package/dist/tasks/add.js +98 -23
  75. package/dist/tasks/add.js.map +1 -1
  76. package/dist/tasks/complete.d.ts.map +1 -1
  77. package/dist/tasks/complete.js +4 -1
  78. package/dist/tasks/complete.js.map +1 -1
  79. package/dist/tasks/find.d.ts.map +1 -1
  80. package/dist/tasks/find.js +4 -1
  81. package/dist/tasks/find.js.map +1 -1
  82. package/dist/tasks/labels.d.ts.map +1 -1
  83. package/dist/tasks/labels.js +4 -1
  84. package/dist/tasks/labels.js.map +1 -1
  85. package/dist/tasks/relates.d.ts.map +1 -1
  86. package/dist/tasks/relates.js +16 -4
  87. package/dist/tasks/relates.js.map +1 -1
  88. package/dist/tasks/show.d.ts.map +1 -1
  89. package/dist/tasks/show.js +4 -1
  90. package/dist/tasks/show.js.map +1 -1
  91. package/dist/tasks/update.d.ts.map +1 -1
  92. package/dist/tasks/update.js +32 -6
  93. package/dist/tasks/update.js.map +1 -1
  94. package/dist/validation/engine.d.ts.map +1 -1
  95. package/dist/validation/engine.js +16 -4
  96. package/dist/validation/engine.js.map +1 -1
  97. package/dist/validation/param-utils.d.ts +5 -3
  98. package/dist/validation/param-utils.d.ts.map +1 -1
  99. package/dist/validation/param-utils.js +8 -6
  100. package/dist/validation/param-utils.js.map +1 -1
  101. package/dist/validation/protocols/_shared.d.ts.map +1 -1
  102. package/dist/validation/protocols/_shared.js +13 -6
  103. package/dist/validation/protocols/_shared.js.map +1 -1
  104. package/package.json +7 -7
  105. package/src/adapters/__tests__/manager.test.ts +0 -1
  106. package/src/codebase-map/analyzers/architecture.ts +0 -1
  107. package/src/conduit/__tests__/local-credential-flow.test.ts +20 -18
  108. package/src/conduit/__tests__/local-transport.test.ts +14 -12
  109. package/src/conduit/local-transport.ts +23 -13
  110. package/src/config.ts +0 -1
  111. package/src/errors.ts +24 -0
  112. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +2 -5
  113. package/src/init.ts +1 -2
  114. package/src/internal.ts +49 -2
  115. package/src/lifecycle/cant/lifecycle-rcasd.cant +133 -0
  116. package/src/memory/__tests__/engine-compat.test.ts +2 -2
  117. package/src/memory/__tests__/pipeline-manifest-sqlite.test.ts +4 -4
  118. package/src/observability/__tests__/index.test.ts +4 -4
  119. package/src/observability/__tests__/log-filter.test.ts +4 -4
  120. package/src/output.ts +73 -75
  121. package/src/sessions/__tests__/session-grade.integration.test.ts +1 -1
  122. package/src/sessions/__tests__/session-grade.test.ts +2 -2
  123. package/src/skills/__tests__/dynamic-skill-generator.test.ts +0 -2
  124. package/src/skills/dynamic-skill-generator.ts +0 -2
  125. package/src/store/__tests__/agent-registry-accessor.test.ts +807 -0
  126. package/src/store/__tests__/api-key-kdf.test.ts +113 -0
  127. package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
  128. package/src/store/__tests__/global-salt.test.ts +195 -0
  129. package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
  130. package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
  131. package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
  132. package/src/store/__tests__/sqlite-backup.test.ts +5 -1
  133. package/src/store/__tests__/t310-integration.test.ts +1150 -0
  134. package/src/store/agent-registry-accessor.ts +847 -140
  135. package/src/store/api-key-kdf.ts +104 -0
  136. package/src/store/conduit-sqlite.ts +655 -0
  137. package/src/store/global-salt.ts +175 -0
  138. package/src/store/migrate-signaldock-to-conduit.ts +669 -0
  139. package/src/store/signaldock-sqlite.ts +431 -254
  140. package/src/store/sqlite-backup.ts +185 -10
  141. package/src/system/backup.ts +2 -62
  142. package/src/system/runtime.ts +4 -6
  143. package/src/tasks/__tests__/error-hints.test.ts +256 -0
  144. package/src/tasks/add.ts +99 -9
  145. package/src/tasks/complete.ts +4 -1
  146. package/src/tasks/find.ts +4 -1
  147. package/src/tasks/labels.ts +4 -1
  148. package/src/tasks/relates.ts +16 -4
  149. package/src/tasks/show.ts +4 -1
  150. package/src/tasks/update.ts +32 -3
  151. package/src/validation/__tests__/error-hints.test.ts +97 -0
  152. package/src/validation/engine.ts +16 -1
  153. package/src/validation/param-utils.ts +10 -7
  154. package/src/validation/protocols/_shared.ts +14 -6
  155. package/src/validation/protocols/cant/architecture-decision.cant +80 -0
  156. package/src/validation/protocols/cant/artifact-publish.cant +95 -0
  157. package/src/validation/protocols/cant/consensus.cant +74 -0
  158. package/src/validation/protocols/cant/contribution.cant +82 -0
  159. package/src/validation/protocols/cant/decomposition.cant +92 -0
  160. package/src/validation/protocols/cant/implementation.cant +67 -0
  161. package/src/validation/protocols/cant/provenance.cant +88 -0
  162. package/src/validation/protocols/cant/release.cant +96 -0
  163. package/src/validation/protocols/cant/research.cant +66 -0
  164. package/src/validation/protocols/cant/specification.cant +67 -0
  165. package/src/validation/protocols/cant/testing.cant +88 -0
  166. package/src/validation/protocols/cant/validation.cant +65 -0
  167. package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
  168. package/templates/config.template.json +0 -1
  169. 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
- expect(Object.keys(all).sort()).toEqual(['brain', 'tasks']);
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
  });