@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
|
@@ -2,22 +2,36 @@
|
|
|
2
2
|
* SQLite backup via VACUUM INTO with snapshot rotation.
|
|
3
3
|
*
|
|
4
4
|
* Produces self-contained, WAL-free copies of CLEO SQLite databases
|
|
5
|
-
* (tasks.db, brain.db at project tier; nexus.db at
|
|
6
|
-
* `.cleo/backups/sqlite/` (project) or
|
|
7
|
-
* (global) with a configurable rotation
|
|
8
|
-
* backup
|
|
5
|
+
* (tasks.db, brain.db, conduit.db at project tier; nexus.db, signaldock.db at
|
|
6
|
+
* global tier) into `.cleo/backups/sqlite/` (project) or
|
|
7
|
+
* `$XDG_DATA_HOME/cleo/backups/sqlite/` (global) with a configurable rotation
|
|
8
|
+
* limit. Also provides raw-file backup for the global-salt binary (not SQLite).
|
|
9
|
+
* All errors are swallowed — backup failure must never interrupt normal operation.
|
|
9
10
|
*
|
|
10
11
|
* @task T4873
|
|
11
12
|
* @task T5158 — extended to cover brain.db
|
|
12
13
|
* @task T306 — extended to cover global-tier nexus.db (epic T299)
|
|
14
|
+
* @task T369 — extended to cover conduit.db (project), signaldock.db (global),
|
|
15
|
+
* and global-salt raw-file backup (epic T310)
|
|
13
16
|
* @epic T4867
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
chmodSync,
|
|
21
|
+
copyFileSync,
|
|
22
|
+
existsSync,
|
|
23
|
+
mkdirSync,
|
|
24
|
+
readdirSync,
|
|
25
|
+
statSync,
|
|
26
|
+
unlinkSync,
|
|
27
|
+
} from 'node:fs';
|
|
17
28
|
import { join } from 'node:path';
|
|
18
29
|
import { getCleoDir, getCleoHome } from '../paths.js';
|
|
19
30
|
import { getBrainNativeDb } from './brain-sqlite.js';
|
|
31
|
+
import { getConduitNativeDb } from './conduit-sqlite.js';
|
|
32
|
+
import { getGlobalSaltPath } from './global-salt.js';
|
|
20
33
|
import { getNexusNativeDb } from './nexus-sqlite.js';
|
|
34
|
+
import { getGlobalSignaldockNativeDb } from './signaldock-sqlite.js';
|
|
21
35
|
import { getNativeDb } from './sqlite.js';
|
|
22
36
|
|
|
23
37
|
/** Maximum number of snapshots retained per database (oldest rotated out). */
|
|
@@ -46,11 +60,16 @@ interface SnapshotTarget {
|
|
|
46
60
|
|
|
47
61
|
/**
|
|
48
62
|
* Canonical list of snapshot targets. Ordering is insertion order — tasks.db
|
|
49
|
-
* snapshots first (highest-value operational state), then brain.db
|
|
63
|
+
* snapshots first (highest-value operational state), then brain.db, then
|
|
64
|
+
* conduit.db (project messaging state).
|
|
65
|
+
*
|
|
66
|
+
* @task T369
|
|
67
|
+
* @epic T310
|
|
50
68
|
*/
|
|
51
69
|
const SNAPSHOT_TARGETS: SnapshotTarget[] = [
|
|
52
70
|
{ prefix: 'tasks', getDb: getNativeDb },
|
|
53
71
|
{ prefix: 'brain', getDb: getBrainNativeDb },
|
|
72
|
+
{ prefix: 'conduit', getDb: getConduitNativeDb }, // Added T369 — project messaging DB
|
|
54
73
|
];
|
|
55
74
|
|
|
56
75
|
/**
|
|
@@ -308,10 +327,16 @@ export function listSqliteBackupsAll(
|
|
|
308
327
|
export type BackupScope = 'project' | 'global';
|
|
309
328
|
|
|
310
329
|
/**
|
|
311
|
-
* Registered global-tier snapshot targets. `
|
|
312
|
-
*
|
|
330
|
+
* Registered global-tier snapshot targets. Both `nexus` and `signaldock` are
|
|
331
|
+
* active as of T369 (epic T310).
|
|
332
|
+
*
|
|
333
|
+
* @task T369
|
|
334
|
+
* @epic T310
|
|
313
335
|
*/
|
|
314
|
-
const GLOBAL_SNAPSHOT_TARGETS: SnapshotTarget[] = [
|
|
336
|
+
const GLOBAL_SNAPSHOT_TARGETS: SnapshotTarget[] = [
|
|
337
|
+
{ prefix: 'nexus', getDb: getNexusNativeDb },
|
|
338
|
+
{ prefix: 'signaldock', getDb: getGlobalSignaldockNativeDb }, // Activated T369 — global agent registry
|
|
339
|
+
];
|
|
315
340
|
|
|
316
341
|
/**
|
|
317
342
|
* Resolve the global-tier backup directory, creating it on first use.
|
|
@@ -333,12 +358,13 @@ function resolveGlobalBackupDir(cleoHomeOverride?: string): string {
|
|
|
333
358
|
* Non-fatal: errors from any individual step are surfaced via the return value
|
|
334
359
|
* but never thrown — a failed snapshot MUST NOT interrupt normal operation.
|
|
335
360
|
*
|
|
336
|
-
* @param dbName - Which global-tier DB to snapshot (`'nexus'
|
|
361
|
+
* @param dbName - Which global-tier DB to snapshot (`'nexus'` or `'signaldock'`)
|
|
337
362
|
* @param opts.rotation - Maximum retained snapshots per prefix (default 10)
|
|
338
363
|
* @param opts.cleoHomeOverride - Override `getCleoHome()` path (use in tests to target a tmp dir)
|
|
339
364
|
* @returns Object containing the new snapshot path and any rotated (deleted) file paths
|
|
340
365
|
*
|
|
341
366
|
* @task T306
|
|
367
|
+
* @task T369 — activated signaldock target (epic T310)
|
|
342
368
|
* @epic T299
|
|
343
369
|
* @why ADR-036 §Backup Mechanism requires VACUUM INTO rotation at the global tier;
|
|
344
370
|
* nexus.db has zero backup coverage prior to v2026.4.11.
|
|
@@ -453,3 +479,152 @@ export function listGlobalSqliteBackups(
|
|
|
453
479
|
return [];
|
|
454
480
|
}
|
|
455
481
|
}
|
|
482
|
+
|
|
483
|
+
// ============================================================================
|
|
484
|
+
// Global-salt raw-file backup (ADR-037 §5)
|
|
485
|
+
// @task T369
|
|
486
|
+
// @epic T310
|
|
487
|
+
// ============================================================================
|
|
488
|
+
|
|
489
|
+
/** Filename prefix for global-salt backup files. */
|
|
490
|
+
const GLOBAL_SALT_BACKUP_PREFIX = 'global-salt';
|
|
491
|
+
|
|
492
|
+
/** Regex matching global-salt backup filenames: `global-salt-YYYYMMDD-HHmmss`. */
|
|
493
|
+
const GLOBAL_SALT_BACKUP_PATTERN = /^global-salt-\d{8}-\d{6}$/;
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Resolve the backup directory for global-salt files: `{cleoHome}/backups/`.
|
|
497
|
+
* Global-salt backups live directly under `backups/` (not `backups/sqlite/`)
|
|
498
|
+
* to make clear they are binary files, not SQLite databases.
|
|
499
|
+
*/
|
|
500
|
+
function resolveGlobalSaltBackupDir(cleoHomeOverride?: string): string {
|
|
501
|
+
const base = cleoHomeOverride ?? getCleoHome();
|
|
502
|
+
return join(base, 'backups');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Rotate global-salt backup files: delete the oldest until fewer than
|
|
507
|
+
* {@link MAX_SNAPSHOTS} remain. Returns the paths of deleted files.
|
|
508
|
+
* Non-fatal on any filesystem error.
|
|
509
|
+
*/
|
|
510
|
+
function rotateGlobalSaltBackups(backupDir: string): string[] {
|
|
511
|
+
const rotated: string[] = [];
|
|
512
|
+
try {
|
|
513
|
+
const files = readdirSync(backupDir)
|
|
514
|
+
.filter((f) => GLOBAL_SALT_BACKUP_PATTERN.test(f))
|
|
515
|
+
.map((f) => ({
|
|
516
|
+
name: f,
|
|
517
|
+
path: join(backupDir, f),
|
|
518
|
+
mtimeMs: statSync(join(backupDir, f)).mtimeMs,
|
|
519
|
+
}))
|
|
520
|
+
.sort((a, b) => a.mtimeMs - b.mtimeMs); // oldest first
|
|
521
|
+
|
|
522
|
+
while (files.length >= MAX_SNAPSHOTS) {
|
|
523
|
+
const oldest = files.shift();
|
|
524
|
+
if (!oldest) break;
|
|
525
|
+
try {
|
|
526
|
+
unlinkSync(oldest.path);
|
|
527
|
+
rotated.push(oldest.path);
|
|
528
|
+
} catch {
|
|
529
|
+
// non-fatal rotation failure
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
} catch {
|
|
533
|
+
// non-fatal
|
|
534
|
+
}
|
|
535
|
+
return rotated;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Creates a raw-file backup of the global-salt binary at
|
|
540
|
+
* `${getCleoHome()}/backups/global-salt-YYYYMMDD-HHmmss` with `0o600`
|
|
541
|
+
* permissions. Rotates to {@link MAX_SNAPSHOTS} (10) copies, deleting the
|
|
542
|
+
* oldest when the limit is reached.
|
|
543
|
+
*
|
|
544
|
+
* Non-fatal: errors are swallowed — salt backup failure must never block cleo.
|
|
545
|
+
* Returns empty strings and no rotated paths on failure.
|
|
546
|
+
*
|
|
547
|
+
* @param opts.cleoHomeOverride - Override `getCleoHome()` path (use in tests to target a tmp dir)
|
|
548
|
+
* @returns Object with the new snapshot path and any rotated (deleted) file paths
|
|
549
|
+
*
|
|
550
|
+
* @task T369
|
|
551
|
+
* @epic T310
|
|
552
|
+
* @why ADR-037 §5 — global-salt is security-critical; losing it invalidates
|
|
553
|
+
* all API keys. Backup enables recovery from accidental deletion.
|
|
554
|
+
*/
|
|
555
|
+
export async function backupGlobalSalt(opts?: {
|
|
556
|
+
cleoHomeOverride?: string;
|
|
557
|
+
}): Promise<{ snapshotPath: string; rotated: string[] }> {
|
|
558
|
+
try {
|
|
559
|
+
const cleoHome = opts?.cleoHomeOverride ?? getCleoHome();
|
|
560
|
+
const saltSourcePath = opts?.cleoHomeOverride
|
|
561
|
+
? join(cleoHome, 'global-salt')
|
|
562
|
+
: getGlobalSaltPath();
|
|
563
|
+
|
|
564
|
+
if (!existsSync(saltSourcePath)) {
|
|
565
|
+
return { snapshotPath: '', rotated: [] };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const backupDir = resolveGlobalSaltBackupDir(opts?.cleoHomeOverride);
|
|
569
|
+
mkdirSync(backupDir, { recursive: true });
|
|
570
|
+
|
|
571
|
+
const rotated = rotateGlobalSaltBackups(backupDir);
|
|
572
|
+
|
|
573
|
+
const snapshotName = `${GLOBAL_SALT_BACKUP_PREFIX}-${formatTimestamp(new Date())}`;
|
|
574
|
+
const snapshotPath = join(backupDir, snapshotName);
|
|
575
|
+
|
|
576
|
+
copyFileSync(saltSourcePath, snapshotPath);
|
|
577
|
+
chmodSync(snapshotPath, 0o600);
|
|
578
|
+
|
|
579
|
+
return { snapshotPath, rotated };
|
|
580
|
+
} catch {
|
|
581
|
+
// non-fatal — backup failure must never interrupt normal operation
|
|
582
|
+
return { snapshotPath: '', rotated: [] };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* A single entry returned by {@link listGlobalSaltBackups}.
|
|
588
|
+
*
|
|
589
|
+
* @task T369
|
|
590
|
+
* @epic T310
|
|
591
|
+
*/
|
|
592
|
+
export interface GlobalSaltBackupEntry {
|
|
593
|
+
/** Backup filename, e.g. `global-salt-20260408-143022`. */
|
|
594
|
+
name: string;
|
|
595
|
+
/** Absolute path to the backup file. */
|
|
596
|
+
path: string;
|
|
597
|
+
/** File size in bytes (should be 32 for a valid global-salt). */
|
|
598
|
+
size: number;
|
|
599
|
+
/** Last-modified timestamp. */
|
|
600
|
+
mtime: Date;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* List global-salt backup files from `$XDG_DATA_HOME/cleo/backups/`, sorted
|
|
605
|
+
* newest-first by mtime.
|
|
606
|
+
*
|
|
607
|
+
* Returns an empty array when the backup directory does not exist.
|
|
608
|
+
*
|
|
609
|
+
* @param cleoHomeOverride - Override `getCleoHome()` path (use in tests to target a tmp dir)
|
|
610
|
+
*
|
|
611
|
+
* @task T369
|
|
612
|
+
* @epic T310
|
|
613
|
+
*/
|
|
614
|
+
export function listGlobalSaltBackups(cleoHomeOverride?: string): GlobalSaltBackupEntry[] {
|
|
615
|
+
try {
|
|
616
|
+
const backupDir = resolveGlobalSaltBackupDir(cleoHomeOverride);
|
|
617
|
+
if (!existsSync(backupDir)) return [];
|
|
618
|
+
|
|
619
|
+
return readdirSync(backupDir)
|
|
620
|
+
.filter((f) => GLOBAL_SALT_BACKUP_PATTERN.test(f))
|
|
621
|
+
.map((f) => {
|
|
622
|
+
const filePath = join(backupDir, f);
|
|
623
|
+
const s = statSync(filePath);
|
|
624
|
+
return { name: f, path: filePath, size: s.size, mtime: new Date(s.mtimeMs) };
|
|
625
|
+
})
|
|
626
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); // newest first
|
|
627
|
+
} catch {
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T310-readiness gate: detect conduit.db vs legacy signaldock.db at project tier.
|
|
3
|
+
*
|
|
4
|
+
* T311 backup/restore code paths reference `.cleo/conduit.db` (project tier) and
|
|
5
|
+
* `$XDG_DATA_HOME/cleo/signaldock.db` (global tier) per ADR-037. A project that
|
|
6
|
+
* has not yet been migrated (still has `.cleo/signaldock.db` without a
|
|
7
|
+
* `.cleo/conduit.db`) will confuse T311 export/import commands. This gate runs
|
|
8
|
+
* as a precondition on every T311 CLI verb.
|
|
9
|
+
*
|
|
10
|
+
* @task T342
|
|
11
|
+
* @epic T311
|
|
12
|
+
* @why T311 export/import references .cleo/conduit.db (project tier) and
|
|
13
|
+
* $XDG_DATA_HOME/cleo/signaldock.db (global tier) per ADR-037.
|
|
14
|
+
* If the current project is still on the pre-T310 topology, T311
|
|
15
|
+
* commands must surface a clear error telling the user to run a
|
|
16
|
+
* cleo command first to trigger migration.
|
|
17
|
+
* @what Throws T310MigrationRequiredError with instructions if legacy
|
|
18
|
+
* signaldock.db exists AND conduit.db does not.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { getProjectRoot } from '../paths.js';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Error class
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Thrown by `assertT310Ready` when the current project is still on the
|
|
31
|
+
* pre-T310 topology: `.cleo/signaldock.db` is present but `.cleo/conduit.db`
|
|
32
|
+
* is absent. T311 commands cannot proceed until migration has run.
|
|
33
|
+
*
|
|
34
|
+
* @task T342
|
|
35
|
+
* @epic T311
|
|
36
|
+
*/
|
|
37
|
+
export class T310MigrationRequiredError extends Error {
|
|
38
|
+
/**
|
|
39
|
+
* @param projectRoot - Absolute path to the project root that needs migration.
|
|
40
|
+
*/
|
|
41
|
+
constructor(public readonly projectRoot: string) {
|
|
42
|
+
super(
|
|
43
|
+
`T310 migration required: .cleo/signaldock.db still exists at ${projectRoot} ` +
|
|
44
|
+
`without a .cleo/conduit.db. Run any cleo command from within the project ` +
|
|
45
|
+
`(e.g. \`cleo version\`) to trigger the automatic T310 migration, then retry.`,
|
|
46
|
+
);
|
|
47
|
+
this.name = 'T310MigrationRequiredError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Public API
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Asserts the current project is on the post-T310 topology. Does nothing
|
|
57
|
+
* if conduit.db exists (migration already ran) OR if no legacy signaldock.db
|
|
58
|
+
* exists (fresh install — no migration needed).
|
|
59
|
+
*
|
|
60
|
+
* Throws when legacy signaldock.db is present AND conduit.db is absent,
|
|
61
|
+
* which indicates the project has not yet been migrated to the T310 topology
|
|
62
|
+
* expected by T311 backup/restore commands.
|
|
63
|
+
*
|
|
64
|
+
* @param projectRoot - Absolute path to the project root. Defaults to
|
|
65
|
+
* `getProjectRoot()` (walks ancestors for `.cleo/` sentinel).
|
|
66
|
+
* @throws {T310MigrationRequiredError} if legacy signaldock.db exists
|
|
67
|
+
* without conduit.db at the project tier.
|
|
68
|
+
*
|
|
69
|
+
* @task T342
|
|
70
|
+
* @epic T311
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* // Precondition guard at the top of every T311 CLI verb handler:
|
|
75
|
+
* assertT310Ready();
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function assertT310Ready(projectRoot?: string): void {
|
|
79
|
+
const root = projectRoot ?? getProjectRoot();
|
|
80
|
+
const legacyPath = join(root, '.cleo', 'signaldock.db');
|
|
81
|
+
const conduitPath = join(root, '.cleo', 'conduit.db');
|
|
82
|
+
|
|
83
|
+
if (existsSync(legacyPath) && !existsSync(conduitPath)) {
|
|
84
|
+
throw new T310MigrationRequiredError(root);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns true if T311 commands can safely run on the current project.
|
|
90
|
+
*
|
|
91
|
+
* This is the non-throwing companion to `assertT310Ready`. Returns false
|
|
92
|
+
* only when the pre-T310 topology is detected (legacy signaldock.db exists
|
|
93
|
+
* without conduit.db). All other states — fresh installs, fully-migrated
|
|
94
|
+
* projects — return true.
|
|
95
|
+
*
|
|
96
|
+
* @param projectRoot - Absolute path to the project root. Defaults to
|
|
97
|
+
* `getProjectRoot()` (walks ancestors for `.cleo/` sentinel).
|
|
98
|
+
* @returns `true` if the project is on the post-T310 topology or is a fresh
|
|
99
|
+
* install; `false` if migration is required.
|
|
100
|
+
*
|
|
101
|
+
* @task T342
|
|
102
|
+
* @epic T311
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* if (!isT310Ready()) {
|
|
107
|
+
* console.error('Run `cleo version` to trigger T310 migration first.');
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export function isT310Ready(projectRoot?: string): boolean {
|
|
112
|
+
try {
|
|
113
|
+
assertT310Ready(projectRoot);
|
|
114
|
+
return true;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof T310MigrationRequiredError) return false;
|
|
117
|
+
throw err;
|
|
118
|
+
}
|
|
119
|
+
}
|
package/src/system/backup.ts
CHANGED
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
*
|
|
23
23
|
* @task T4783
|
|
24
24
|
* @task T5158 — extended to use VACUUM INTO for .db files and atomicWrite for JSON
|
|
25
|
-
* @task T306 — extended to support global-tier scope (nexus.db; epic T299)
|
|
26
25
|
*/
|
|
27
26
|
|
|
28
27
|
import {
|
|
@@ -87,15 +86,6 @@ export interface BackupResult {
|
|
|
87
86
|
type: string;
|
|
88
87
|
/** Files that were successfully captured into this backup. */
|
|
89
88
|
files: string[];
|
|
90
|
-
/**
|
|
91
|
-
* Global-tier snapshot results, populated when `opts.includeGlobal` is true.
|
|
92
|
-
* Key is the DB name (e.g. `'nexus'`), value is the snapshot path or an
|
|
93
|
-
* empty string when the DB was not initialized.
|
|
94
|
-
*
|
|
95
|
-
* @task T306
|
|
96
|
-
* @epic T299
|
|
97
|
-
*/
|
|
98
|
-
global?: Record<string, string>;
|
|
99
89
|
}
|
|
100
90
|
|
|
101
91
|
/** Result shape returned by {@link restoreBackup}. */
|
|
@@ -122,31 +112,12 @@ export interface RestoreResult {
|
|
|
122
112
|
* when `safeSqliteSnapshot` asks for them. This makes the function
|
|
123
113
|
* self-contained — callers do not need to pre-open the DBs.
|
|
124
114
|
*
|
|
125
|
-
* When `opts.includeGlobal` is true, also snapshots global-tier databases
|
|
126
|
-
* (currently `nexus.db`) via {@link vacuumIntoGlobalBackup}. Global snapshots
|
|
127
|
-
* are written to `$XDG_DATA_HOME/cleo/backups/sqlite/` and returned in
|
|
128
|
-
* `result.global`.
|
|
129
|
-
*
|
|
130
115
|
* Async because opening the database engines requires async migration
|
|
131
116
|
* reconciliation (ADR-012). The CLI dispatch layer awaits this result.
|
|
132
|
-
*
|
|
133
|
-
* @task T306
|
|
134
|
-
* @epic T299
|
|
135
117
|
*/
|
|
136
118
|
export async function createBackup(
|
|
137
119
|
projectRoot: string,
|
|
138
|
-
opts?: {
|
|
139
|
-
type?: string;
|
|
140
|
-
note?: string;
|
|
141
|
-
/**
|
|
142
|
-
* When true, also snapshot global-tier databases (nexus.db).
|
|
143
|
-
* Defaults to false for backwards compatibility.
|
|
144
|
-
*
|
|
145
|
-
* @task T306
|
|
146
|
-
* @epic T299
|
|
147
|
-
*/
|
|
148
|
-
includeGlobal?: boolean;
|
|
149
|
-
},
|
|
120
|
+
opts?: { type?: string; note?: string },
|
|
150
121
|
): Promise<BackupResult> {
|
|
151
122
|
const cleoDir = join(projectRoot, '.cleo');
|
|
152
123
|
const btype = opts?.type || 'snapshot';
|
|
@@ -241,38 +212,7 @@ export async function createBackup(
|
|
|
241
212
|
// non-fatal
|
|
242
213
|
}
|
|
243
214
|
|
|
244
|
-
|
|
245
|
-
// @task T306 @epic T299
|
|
246
|
-
const globalResults: Record<string, string> = {};
|
|
247
|
-
if (opts?.includeGlobal) {
|
|
248
|
-
// Ensure nexus.db is initialized so its native handle is available.
|
|
249
|
-
try {
|
|
250
|
-
const { getNexusDb } = await import('../store/nexus-sqlite.js');
|
|
251
|
-
await getNexusDb();
|
|
252
|
-
} catch {
|
|
253
|
-
// nexus.db open failed — vacuumIntoGlobalBackup will return empty path
|
|
254
|
-
}
|
|
255
|
-
try {
|
|
256
|
-
const { vacuumIntoGlobalBackup } = await import('../store/sqlite-backup.js');
|
|
257
|
-
const nexusResult = await vacuumIntoGlobalBackup('nexus');
|
|
258
|
-
globalResults['nexus'] = nexusResult.snapshotPath;
|
|
259
|
-
} catch {
|
|
260
|
-
// non-fatal — global backup failure must not block project backup
|
|
261
|
-
globalResults['nexus'] = '';
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const result: BackupResult = {
|
|
266
|
-
backupId,
|
|
267
|
-
path: backupDir,
|
|
268
|
-
timestamp,
|
|
269
|
-
type: btype,
|
|
270
|
-
files: backedUp,
|
|
271
|
-
};
|
|
272
|
-
if (opts?.includeGlobal) {
|
|
273
|
-
result.global = globalResults;
|
|
274
|
-
}
|
|
275
|
-
return result;
|
|
215
|
+
return { backupId, path: backupDir, timestamp, type: btype, files: backedUp };
|
|
276
216
|
}
|
|
277
217
|
|
|
278
218
|
/** A single backup entry returned by listSystemBackups. */
|
package/src/system/runtime.ts
CHANGED
|
@@ -35,8 +35,6 @@ export interface RuntimeDiagnostics {
|
|
|
35
35
|
};
|
|
36
36
|
naming: {
|
|
37
37
|
cli: string;
|
|
38
|
-
/** Legacy field. CLI dispatch only. */
|
|
39
|
-
mcp: string;
|
|
40
38
|
server: string;
|
|
41
39
|
};
|
|
42
40
|
node: string;
|
|
@@ -73,14 +71,14 @@ function detectFromDataRoot(dataRoot: string): RuntimeChannel | null {
|
|
|
73
71
|
return null;
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
function getExpectedNaming(channel: RuntimeChannel): { cli: string;
|
|
74
|
+
function getExpectedNaming(channel: RuntimeChannel): { cli: string; server: string } {
|
|
77
75
|
switch (channel) {
|
|
78
76
|
case 'dev':
|
|
79
|
-
return { cli: 'cleo-dev',
|
|
77
|
+
return { cli: 'cleo-dev', server: 'cleo-dev' };
|
|
80
78
|
case 'beta':
|
|
81
|
-
return { cli: 'cleo-beta',
|
|
79
|
+
return { cli: 'cleo-beta', server: 'cleo-beta' };
|
|
82
80
|
default:
|
|
83
|
-
return { cli: 'cleo',
|
|
81
|
+
return { cli: 'cleo', server: 'cleo' };
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
|