@cleocode/contracts 2026.5.108 → 2026.5.110

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 (51) hide show
  1. package/dist/__tests__/db-inventory.test.d.ts +25 -0
  2. package/dist/__tests__/db-inventory.test.d.ts.map +1 -0
  3. package/dist/__tests__/db-inventory.test.js +148 -0
  4. package/dist/__tests__/db-inventory.test.js.map +1 -0
  5. package/dist/__tests__/memory-wire-shapes.test.js.map +1 -1
  6. package/dist/__tests__/task-type-narrowing.test.d.ts +14 -0
  7. package/dist/__tests__/task-type-narrowing.test.d.ts.map +1 -0
  8. package/dist/__tests__/task-type-narrowing.test.js +51 -0
  9. package/dist/__tests__/task-type-narrowing.test.js.map +1 -0
  10. package/dist/backup-verify.d.ts +136 -0
  11. package/dist/backup-verify.d.ts.map +1 -0
  12. package/dist/backup-verify.js +29 -0
  13. package/dist/backup-verify.js.map +1 -0
  14. package/dist/brain.d.ts +103 -0
  15. package/dist/brain.d.ts.map +1 -1
  16. package/dist/db-inventory.d.ts +200 -0
  17. package/dist/db-inventory.d.ts.map +1 -0
  18. package/dist/db-inventory.js +76 -0
  19. package/dist/db-inventory.js.map +1 -0
  20. package/dist/db-inventory.json +165 -0
  21. package/dist/db-recovery.d.ts +141 -0
  22. package/dist/db-recovery.d.ts.map +1 -0
  23. package/dist/db-recovery.js +29 -0
  24. package/dist/db-recovery.js.map +1 -0
  25. package/dist/doctor.d.ts +605 -0
  26. package/dist/doctor.d.ts.map +1 -1
  27. package/dist/index.d.ts +6 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/memory/observe.d.ts +8 -0
  32. package/dist/memory/observe.d.ts.map +1 -1
  33. package/dist/operations/tasks.d.ts +4 -4
  34. package/dist/operations/tasks.d.ts.map +1 -1
  35. package/dist/task.d.ts +13 -2
  36. package/dist/task.d.ts.map +1 -1
  37. package/dist/task.js.map +1 -1
  38. package/package.json +5 -4
  39. package/src/__tests__/db-inventory.test.ts +184 -0
  40. package/src/__tests__/memory-wire-shapes.test.ts +1 -0
  41. package/src/__tests__/task-type-narrowing.test.ts +56 -0
  42. package/src/backup-verify.ts +140 -0
  43. package/src/brain.ts +114 -0
  44. package/src/db-inventory.json +165 -0
  45. package/src/db-inventory.ts +234 -0
  46. package/src/db-recovery.ts +144 -0
  47. package/src/doctor.ts +634 -0
  48. package/src/index.ts +46 -0
  49. package/src/memory/observe.ts +8 -0
  50. package/src/operations/tasks.ts +4 -4
  51. package/src/task.ts +13 -2
@@ -0,0 +1,234 @@
1
+ /**
2
+ * CLEO Database Inventory — SSoT type contract + immutable constant.
3
+ *
4
+ * @remarks
5
+ * This module exports the typed shape and the runtime constant for every
6
+ * SQLite database that CLEO manages. The runtime data is sourced from the
7
+ * canonical JSON inventory at `packages/contracts/src/db-inventory.json`
8
+ * via ESM JSON import (`with { type: 'json' }`). The JSON file is the
9
+ * single source of truth; this module re-exposes it with a typed contract.
10
+ *
11
+ * The JSON file is co-located in this package (rather than referencing a
12
+ * sibling-package `src/` path) so that the built `dist/db-inventory.js`
13
+ * resolves the JSON via a relative `./db-inventory.json` import that
14
+ * works inside the published `@cleocode/contracts` npm tarball — sibling
15
+ * package `src/` directories are not shipped to consumers.
16
+ *
17
+ * Downstream consumers:
18
+ *
19
+ * - **T10307 — Fleet survey** (`cleo doctor db-substrate`) iterates `DB_INVENTORY`
20
+ * to enumerate every DB on disk across project + global tiers.
21
+ * - **T10310 — Pragma drift** validates that every open handle for each role
22
+ * applies the pragma SSoT (`specs/sqlite-pragmas.json`).
23
+ * - **T10311 — Migration coverage** asserts that each entry's
24
+ * `migrationsDir` exists and contains a `0000_*` baseline.
25
+ * - **T10312 — Doctor integrity** runs `PRAGMA integrity_check` against
26
+ * every entry whose tier resolves to a present file on disk.
27
+ * - **T10320 — Cross-DB invariants** uses `ownerPackage` + `tier` to
28
+ * classify which DBs must agree on shared identifiers (project_id,
29
+ * agent_id, session_id, ...).
30
+ *
31
+ * Adding a new entry to the inventory is a charter amendment — update
32
+ * `packages/contracts/src/db-inventory.json` AND
33
+ * `.cleo/adrs/ADR-068-cleo-database-charter.md` in the same PR (gated by
34
+ * `cleo check arch` Gate-4 SSoT-EXEMPT lint once T10282 ships its drift
35
+ * check).
36
+ *
37
+ * @see ADR-068 — CLEO Database Charter (canonical narrative)
38
+ * @see Saga T10281 — SG-BRAIN-DB-RESILIENCE (provenance)
39
+ * @see Epic T10282 — E1-DB-INVENTORY (this artifact)
40
+ * @see Research note `sg-brain-db-resilience-deep-audit-2026-05-23`
41
+ *
42
+ * @task T10305
43
+ * @epic T10282
44
+ * @saga T10281
45
+ */
46
+
47
+ import dbInventoryData from './db-inventory.json' with { type: 'json' };
48
+
49
+ /**
50
+ * Canonical role names for every SQLite database in the CLEO charter.
51
+ *
52
+ * @remarks
53
+ * Mirrors the `openCleoDb('<role>', cwd)` chokepoint (ADR-068 §3) plus the
54
+ * three audit-discovered roles (`global-brain`, `global-tasks`, `manifest`)
55
+ * that exist on disk today but do not yet have a registered opener.
56
+ *
57
+ * - `tasks` — project-tier task engine DB
58
+ * - `brain` — project-tier memory DB (PII)
59
+ * - `conduit` — project-tier agent message transport
60
+ * - `manifest` — project-tier derived blob index (rebuildable)
61
+ * - `llmtxt` — reserved project-tier llmtxt session store
62
+ * - `nexus` — global-tier cross-project knowledge graph (PII)
63
+ * - `signaldock-project` — historical project-tier signaldock (post-ADR-037, merged into conduit)
64
+ * - `signaldock-global` — global-tier canonical agent identity registry
65
+ * - `telemetry` — global-tier opt-in command telemetry
66
+ * - `skills` — global-tier per-user skill registry
67
+ * - `global-brain` — global-tier brain.db observed on disk (audit §1.2 — provenance under investigation)
68
+ * - `global-tasks` — global-tier tasks.db observed on disk (audit §1.2 — provenance under investigation)
69
+ *
70
+ * @public
71
+ */
72
+ export type DbRole =
73
+ | 'tasks'
74
+ | 'brain'
75
+ | 'conduit'
76
+ | 'manifest'
77
+ | 'llmtxt'
78
+ | 'nexus'
79
+ | 'signaldock-project'
80
+ | 'signaldock-global'
81
+ | 'telemetry'
82
+ | 'skills'
83
+ | 'global-brain'
84
+ | 'global-tasks';
85
+
86
+ /**
87
+ * Lifecycle scope of a CLEO database.
88
+ *
89
+ * @remarks
90
+ * - `project` — file lives under `<projectRoot>/.cleo/`; lifecycle bound to one CLEO project.
91
+ * - `global` — file lives under `$XDG_DATA_HOME/cleo/` (env-paths via `getCleoHome()`).
92
+ * - `derived` — file lives under the project tier but is rebuildable from a
93
+ * non-DB source-of-truth (e.g. content-addressable blob store).
94
+ * MAY be excluded from backup-pack.
95
+ *
96
+ * @public
97
+ */
98
+ export type DbTier = 'project' | 'global' | 'derived';
99
+
100
+ /**
101
+ * Privacy classification governing backup + cloud-export rules.
102
+ *
103
+ * @remarks
104
+ * - `local-only` — never cloud-exported.
105
+ * - `local-only-pii` — never cloud-exported; PII-class — requires
106
+ * explicit `cleo backup export --include-pii`.
107
+ * - `cloud-exportable-opt-in` — may be exported only after explicit opt-in
108
+ * (e.g. `cleo diagnostics enable`).
109
+ *
110
+ * @public
111
+ */
112
+ export type DbPrivacy = 'local-only' | 'local-only-pii' | 'cloud-exportable-opt-in';
113
+
114
+ /**
115
+ * Concurrency model for the database.
116
+ *
117
+ * @remarks
118
+ * Every CLEO database today is `single-writer` (WAL mode with in-process
119
+ * serialization via the `openCleoDb` singleton). The field is enumerated so
120
+ * a future read-replica or multi-writer DB MUST be declared explicitly.
121
+ *
122
+ * @public
123
+ */
124
+ export type DbConcurrency = 'single-writer' | 'multi-writer';
125
+
126
+ /**
127
+ * One row in the CLEO database charter.
128
+ *
129
+ * @remarks
130
+ * Every field is required (no `null` placeholders) EXCEPT `drizzleSchemaPath`
131
+ * and `migrationsDir` — those MAY be `null` for:
132
+ *
133
+ * - `derived` databases whose schema is owned by an upstream library (e.g.
134
+ * `manifest.db` is owned by `llmtxt/blob`'s `BlobFsAdapter` contract).
135
+ * - `reserved` roles whose opener throws `not yet implemented` (e.g.
136
+ * `llmtxt.db`).
137
+ *
138
+ * Path templates use the substitution tokens:
139
+ *
140
+ * - `<projectRoot>` — resolved at runtime from `CLEO_ROOT` env var or
141
+ * `process.cwd()` via `getCleoProjectDir()`.
142
+ * - `$XDG_DATA_HOME` — resolved via env-paths through `getCleoHome()`.
143
+ * Linux: `~/.local/share/cleo/`; macOS:
144
+ * `~/Library/Application Support/cleo/`.
145
+ *
146
+ * @public
147
+ */
148
+ export interface DbInventoryEntry {
149
+ /** Canonical role identifier used by `openCleoDb` and audit tooling. */
150
+ readonly role: DbRole;
151
+ /** Lifecycle scope — project, global, or derived. */
152
+ readonly tier: DbTier;
153
+ /**
154
+ * Filesystem path template with `<projectRoot>` / `$XDG_DATA_HOME` tokens.
155
+ * Substitution convention is documented on {@link DbInventoryEntry}.
156
+ */
157
+ readonly filePathTemplate: string;
158
+ /**
159
+ * Repo-relative path to the Drizzle schema TypeScript file. `null` when
160
+ * the schema is owned by an upstream library or the role is reserved.
161
+ */
162
+ readonly drizzleSchemaPath: string | null;
163
+ /**
164
+ * Repo-relative path to the Drizzle migrations directory (with trailing
165
+ * slash). `null` for `derived` rows or reserved roles. The path MUST
166
+ * exist on disk when non-null — enforced by the contracts test suite.
167
+ */
168
+ readonly migrationsDir: string | null;
169
+ /** npm package that owns the open + lifecycle for this database. */
170
+ readonly ownerPackage: string;
171
+ /**
172
+ * Human-readable description of HOW the database is opened. Typically
173
+ * a code excerpt like `openCleoDb('tasks', cwd)`. For databases without
174
+ * a registered opener, describes provenance / status.
175
+ */
176
+ readonly openedVia: string;
177
+ /** Concurrency model — see {@link DbConcurrency}. */
178
+ readonly concurrency: DbConcurrency;
179
+ /** Privacy classification — see {@link DbPrivacy}. */
180
+ readonly privacy: DbPrivacy;
181
+ /**
182
+ * Canonical backup path template. For `derived` rows this is the
183
+ * sentinel string `rebuildable-from-blob-store` (or similar) and the
184
+ * row is excluded from backup-pack staging.
185
+ */
186
+ readonly backupPath: string;
187
+ /**
188
+ * Provenance pointers — ADR row, related task IDs, audit section
189
+ * references. Comma-or-semicolon separated string of references.
190
+ */
191
+ readonly documentedIn: string;
192
+ }
193
+
194
+ /**
195
+ * Shape of the raw JSON SSoT — a header plus the typed entry array.
196
+ *
197
+ * @internal
198
+ */
199
+ interface DbInventoryFile {
200
+ readonly $schemaNote: string;
201
+ readonly $pathTokens: Readonly<Record<string, string>>;
202
+ readonly entries: readonly DbInventoryEntry[];
203
+ }
204
+
205
+ // Narrow the raw JSON to the typed shape. The runtime parity test
206
+ // (`db-inventory.test.ts`) asserts every field is valid for every entry.
207
+ const rawInventory = dbInventoryData as DbInventoryFile;
208
+
209
+ /**
210
+ * Immutable canonical inventory of every CLEO SQLite database.
211
+ *
212
+ * @remarks
213
+ * Sourced from `packages/contracts/src/db-inventory.json` — the SSoT.
214
+ * Adding a new entry is a charter amendment per ADR-068 §"How to add a
215
+ * new database". The {@link db-inventory.test.ts} suite asserts:
216
+ *
217
+ * 1. Every `role` is unique across the array.
218
+ * 2. Every `drizzleSchemaPath` (when non-null) exists on disk.
219
+ * 3. Every `migrationsDir` (when non-null) exists on disk.
220
+ * 4. Every `tier` is a member of the {@link DbTier} union.
221
+ *
222
+ * @example Iterate every project-tier database
223
+ * ```typescript
224
+ * import { DB_INVENTORY } from '@cleocode/contracts';
225
+ *
226
+ * const projectDbs = DB_INVENTORY.filter((entry) => entry.tier === 'project');
227
+ * for (const entry of projectDbs) {
228
+ * console.log(entry.role, entry.filePathTemplate);
229
+ * }
230
+ * ```
231
+ *
232
+ * @public
233
+ */
234
+ export const DB_INVENTORY: readonly DbInventoryEntry[] = rawInventory.entries;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Generic CLEO database recovery contracts — generalised from the brain-only
3
+ * T10303/T10304 shapes so the same envelope works for every role declared in
4
+ * {@link DB_INVENTORY}.
5
+ *
6
+ * These types mirror {@link BrainRecoveryResult} + {@link BackupRecoverBrainResult}
7
+ * almost verbatim, with two adjustments:
8
+ *
9
+ * 1. `observationsRecovered: number | null` is generalised to
10
+ * `rowCounts: DbRecoveredRowCounts` — a `Record<tableName, number | null>`
11
+ * keyed by the user tables discovered in the restored DB via
12
+ * `sqlite_master`. Tables that fail to count surface as `null`.
13
+ * 2. Every envelope carries the `role: DbRole` it pertains to so envelopes from
14
+ * `cleo backup recover <role>` are self-describing across tools that may
15
+ * fan recovery out across multiple DBs.
16
+ *
17
+ * The brain-specific shapes ({@link BrainRecoveryResult},
18
+ * {@link BackupRecoverBrainResult}, {@link BrainRecoveredRowCounts}) remain in
19
+ * {@link brain.ts} as the backward-compatible surface — the brain CLI verb
20
+ * (`cleo backup recover brain`) still emits the same envelope it did before
21
+ * T10318 generalised the pipeline.
22
+ *
23
+ * @task T10318
24
+ * @epic T10284
25
+ * @saga T10281
26
+ * @adr ADR-068
27
+ */
28
+
29
+ import type { DbRole } from './db-inventory.js';
30
+
31
+ /**
32
+ * Best-effort per-table row counts probed from a recovered CLEO database.
33
+ *
34
+ * @remarks
35
+ * The recovery pipeline enumerates user tables in the restored DB via
36
+ * `sqlite_master` (excluding internal `sqlite_*` and Drizzle journal tables)
37
+ * and runs `SELECT COUNT(*)` on each. Tables that fail to count — for
38
+ * example, a missing table in a very old snapshot, or a schema/version
39
+ * mismatch — surface as `null` instead of throwing so the envelope always
40
+ * serialises cleanly.
41
+ *
42
+ * Keys are exact SQLite table names (case-sensitive). Values are non-negative
43
+ * integers or `null`.
44
+ *
45
+ * @public
46
+ */
47
+ export type DbRecoveredRowCounts = Readonly<Record<string, number | null>>;
48
+
49
+ /**
50
+ * Result of a generic CLEO DB auto-recovery attempt.
51
+ *
52
+ * @remarks
53
+ * Returned by `recoverMalformedDb({ role, ... })` (T10318) when the chokepoint
54
+ * (or an operator invoking `cleo backup recover <role>`) detects malformation
55
+ * via `ERR_SQLITE_ERROR errcode=11` or a failing `PRAGMA integrity_check` /
56
+ * `PRAGMA quick_check`. The corrupt DB has been moved to a quarantine
57
+ * directory and the freshest validated snapshot has been copied to the
58
+ * canonical role path.
59
+ *
60
+ * Consumers read this structure to decide whether to re-attempt the open and
61
+ * to emit user-facing diagnostics with the data-loss window.
62
+ *
63
+ * @public
64
+ */
65
+ export interface DbRecoveryResult {
66
+ /** Canonical role of the database that was recovered. */
67
+ role: DbRole;
68
+ /** Absolute path to the snapshot that was restored, or `null` if recovery failed. */
69
+ restoredFrom: string | null;
70
+ /**
71
+ * Approximate hours between the snapshot's timestamp and the recovery
72
+ * event. `null` when the snapshot timestamp could not be parsed.
73
+ * Used in the recovery warning to make the data-loss window legible.
74
+ */
75
+ dataLossWindowHours: number | null;
76
+ /**
77
+ * Per-table row counts probed from the restored DB. Each key is a user
78
+ * table name; each value is the row count, or `null` on count failure.
79
+ * Empty record `{}` when the restored DB has no user tables (or when
80
+ * the probe itself failed to open the DB).
81
+ */
82
+ rowCounts: DbRecoveredRowCounts;
83
+ /** `true` if the restored DB passes `PRAGMA quick_check`. */
84
+ integrityOK: boolean;
85
+ /**
86
+ * Absolute path to the quarantine directory where the corrupt DB plus
87
+ * its `-wal`/`-shm` sidecars were moved before restore. `null` when no
88
+ * corrupt DB existed at the canonical path (recovery into an empty slot).
89
+ */
90
+ quarantineDir: string | null;
91
+ }
92
+
93
+ /**
94
+ * Envelope payload returned by `cleo backup recover <role>`.
95
+ *
96
+ * @remarks
97
+ * Wraps {@link DbRecoveryResult} with three CLI-only additions:
98
+ *
99
+ * - `rowsRecovered` aliases `rowCounts` to keep the envelope shape compatible
100
+ * with the brain-specific {@link BackupRecoverBrainResult}.
101
+ * - `quarantinedTo: string` is the non-nullable string form (empty string in
102
+ * `--dry-run` mode where no files were touched) — easier for shell scripts
103
+ * that pipe the envelope into `jq` and then path operations.
104
+ * - `dryRun: boolean` records whether the envelope is a *plan* (would-do)
105
+ * versus a *record* (already-done). Consumers MUST check this before
106
+ * treating `restoredFrom` as a post-condition.
107
+ *
108
+ * @task T10318
109
+ * @epic T10284
110
+ * @saga T10281
111
+ * @public
112
+ */
113
+ export interface BackupRecoverResult {
114
+ /** Canonical role of the database that was (or would be) recovered. */
115
+ role: DbRole;
116
+ /**
117
+ * Absolute path to the snapshot that was (or would be) restored. Empty
118
+ * string when no snapshot was available — the envelope's caller surfaces
119
+ * that as a failure mode via a structured error.
120
+ */
121
+ restoredFrom: string;
122
+ /** Per-table row counts probed from the restored DB (or the plan target). */
123
+ rowsRecovered: DbRecoveredRowCounts;
124
+ /**
125
+ * Approximate hours between the snapshot's timestamp and the recovery
126
+ * event. `null` when the snapshot timestamp could not be parsed (e.g.
127
+ * legacy `<role>.db.PRE-DUP-FIX-*` fallback artifact).
128
+ */
129
+ dataLossWindowHours: number | null;
130
+ /** `true` when the restored DB passes `PRAGMA quick_check`. */
131
+ integrityOK: boolean;
132
+ /**
133
+ * Absolute path to the quarantine directory where the corrupt DB plus
134
+ * its `-wal`/`-shm` sidecars were moved. Empty string in `--dry-run`
135
+ * mode where no files were touched.
136
+ */
137
+ quarantinedTo: string;
138
+ /**
139
+ * `true` when invoked with `--dry-run` — indicates the envelope is a
140
+ * plan, NOT a record of mutations performed. Consumers MUST check this
141
+ * before treating `restoredFrom` as a post-condition.
142
+ */
143
+ dryRun: boolean;
144
+ }