@cleocode/contracts 2026.5.108 → 2026.5.109

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/src/doctor.ts CHANGED
@@ -286,6 +286,168 @@ export interface SagaAuditResult {
286
286
  driftCount: number;
287
287
  }
288
288
 
289
+ // ============================================================================
290
+ // DB-Substrate Survey (T10307 — Saga T10281 SG-BRAIN-DB-RESILIENCE / Epic T10282)
291
+ // ============================================================================
292
+
293
+ /**
294
+ * Per-database substrate-audit findings.
295
+ *
296
+ * Produced by `surveyProjectDbSubstrate` for every entry in the
297
+ * `DB_INVENTORY` SSoT. Fields that depend on the file existing on disk
298
+ * (`integrityOK`, `rowCounts`, `lastWriteMs`, `sizeBytes`) are `null` when
299
+ * the file is missing. `error` is non-null when integrity_check or the
300
+ * open / row-count round-trip threw — corrupt DBs surface here so the
301
+ * envelope's `summary.corrupt` counter increments.
302
+ *
303
+ * `suggestedFix` is a stable, machine-readable repair command (typically
304
+ * `cleo backup recover <role>` — the verb introduced by T10304). Always
305
+ * populated when `integrityOK === false` so the operator has a one-line
306
+ * remediation path.
307
+ *
308
+ * @task T10307
309
+ * @epic T10282
310
+ * @saga T10281
311
+ */
312
+ export interface DbSubstrateEntry {
313
+ /** Absolute on-disk path the inventory template resolved to. */
314
+ filePath: string;
315
+ /** `true` iff the file currently exists on disk. */
316
+ exists: boolean;
317
+ /**
318
+ * Result of `PRAGMA integrity_check`. `true` when the pragma returned
319
+ * exactly one `'ok'` row; `false` when it returned anything else or
320
+ * the call threw; `null` when the file did not exist (no integrity
321
+ * check attempted).
322
+ */
323
+ integrityOK: boolean | null;
324
+ /**
325
+ * Row counts for up to 3 representative tables (alphabetically first
326
+ * non-meta tables). `null` when the file is missing or the count
327
+ * round-trip threw.
328
+ */
329
+ rowCounts: Readonly<Record<string, number>> | null;
330
+ /** `fs.statSync(filePath).mtimeMs`, or `null` when the file is missing. */
331
+ lastWriteMs: number | null;
332
+ /** `fs.statSync(filePath).size`, or `null` when the file is missing. */
333
+ sizeBytes: number | null;
334
+ /**
335
+ * Stable error string surfaced from a failed integrity_check or open.
336
+ * `null` when the survey succeeded (including when the file simply
337
+ * does not exist — that is `exists: false`, not an error).
338
+ */
339
+ error: string | null;
340
+ /**
341
+ * Suggested repair command when `integrityOK === false`. `null`
342
+ * otherwise. Typically `cleo backup recover <role>` per T10304.
343
+ */
344
+ suggestedFix: string | null;
345
+ }
346
+
347
+ /**
348
+ * Warning kinds surfaced by `surveyDbSubstrate` via `envelope.meta.warnings`.
349
+ *
350
+ * @remarks
351
+ * Surfacing as warnings (not violations) means they do NOT drive a
352
+ * non-zero exit code on their own — operators should review and remediate,
353
+ * but the substrate is otherwise healthy.
354
+ *
355
+ * - `orphan-project-root` — a `.cleo/` directory found at a project-PARENT
356
+ * path (e.g. `/mnt/projects/.cleo/`). Regression class T9550.
357
+ * - `nested-nexus-duplicate` — `~/.local/share/cleo/nexus/{nexus,signaldock}.db`
358
+ * exists alongside the canonical flat `nexus.db` / `signaldock.db` files,
359
+ * indicating a structural duplicate from an older XDG-path resolution.
360
+ *
361
+ * @task T10307
362
+ */
363
+ export type DbSubstrateWarningKind = 'orphan-project-root' | 'nested-nexus-duplicate';
364
+
365
+ /**
366
+ * One warning entry surfaced in the envelope's `meta.warnings` list.
367
+ *
368
+ * @task T10307
369
+ */
370
+ export interface DbSubstrateWarning {
371
+ /** Machine-readable warning class. */
372
+ kind: DbSubstrateWarningKind;
373
+ /** Absolute path of the offending file or directory. */
374
+ path: string;
375
+ /**
376
+ * `fs.statSync(path).mtimeMs` for the offending file/directory.
377
+ * `null` when stat() threw (e.g. ephemeral race during scan).
378
+ */
379
+ lastWriteMs: number | null;
380
+ }
381
+
382
+ /**
383
+ * Survey result for one CLEO project (i.e. one `.cleo/` directory) plus
384
+ * the global tier of databases owned by it.
385
+ *
386
+ * @remarks
387
+ * `dbs` is keyed by the canonical role names from `DB_INVENTORY`. Every
388
+ * entry is populated regardless of whether the file exists — the caller
389
+ * can iterate every role and decide how to surface absent rows.
390
+ *
391
+ * @task T10307
392
+ */
393
+ export interface DbSubstrateProjectSurvey {
394
+ /** Absolute path to the project root that was surveyed. */
395
+ projectRoot: string;
396
+ /**
397
+ * Stable identifier for the project — currently the `base64url(path)`
398
+ * truncated to 32 chars, matching the convention used by `cleo nexus`
399
+ * project resolution.
400
+ */
401
+ projectId: string;
402
+ /** Per-role survey result, keyed by canonical role name. */
403
+ dbs: Readonly<Record<string, DbSubstrateEntry>>;
404
+ }
405
+
406
+ /**
407
+ * Aggregate counters surfaced in `envelope.data.summary`.
408
+ *
409
+ * @task T10307
410
+ */
411
+ export interface DbSubstrateSummary {
412
+ /** Total inventory entries surveyed across all projects + global tier. */
413
+ totalDbs: number;
414
+ /** Entries where `exists && integrityOK === true`. */
415
+ healthy: number;
416
+ /** Entries where `!exists`. */
417
+ missing: number;
418
+ /** Entries where `exists && integrityOK === false`. */
419
+ corrupt: number;
420
+ }
421
+
422
+ /**
423
+ * Top-level result payload returned by `surveyDbSubstrate`.
424
+ *
425
+ * @remarks
426
+ * `scope === 'project'` means the survey covered exactly one project plus
427
+ * the global tier. `scope === 'fleet'` means the survey walked every
428
+ * `.cleo/` directory discoverable from a fleet-root search path.
429
+ *
430
+ * Carried as the `data` field of the LAFS envelope; the corresponding
431
+ * `meta` field carries `meta.warnings` per `DbSubstrateWarning` and the
432
+ * canonical `operation: 'doctor.db-substrate.run'` identifier.
433
+ *
434
+ * @task T10307
435
+ */
436
+ export interface DbSubstrateAuditResult {
437
+ /** Survey scope. `'project'` = current project; `'fleet'` = many projects. */
438
+ scope: 'project' | 'fleet';
439
+ /** Per-project surveys. One entry for `scope='project'`; ≥1 for `scope='fleet'`. */
440
+ projects: DbSubstrateProjectSurvey[];
441
+ /** Aggregate counters across all surveyed entries. */
442
+ summary: DbSubstrateSummary;
443
+ /**
444
+ * Structural warnings the survey detected outside of any inventory
445
+ * entry — orphan project-root `.cleo/` directories, nested-nexus
446
+ * duplicates, etc. ALWAYS present (may be empty).
447
+ */
448
+ warnings: DbSubstrateWarning[];
449
+ }
450
+
289
451
  /**
290
452
  * One line appended to `.cleo/audit/worktree-prune.jsonl` per prune
291
453
  * operation. Extends the existing audit-log schema (timestamp +
package/src/index.ts CHANGED
@@ -140,10 +140,13 @@ export type {
140
140
  export { BOUNDARY_REGISTRY } from './boundary.js';
141
141
  // === Brain/Memory Types ===
142
142
  export type {
143
+ BackupRecoverBrainResult,
143
144
  BrainCognitiveType,
144
145
  BrainEntryRef,
145
146
  BrainEntrySummary,
146
147
  BrainMemoryTier,
148
+ BrainRecoveredRowCounts,
149
+ BrainRecoveryResult,
147
150
  BrainSourceConfidence,
148
151
  ContradictionDetail,
149
152
  SupersededEntry,
@@ -271,6 +274,15 @@ export type {
271
274
  TaskQueryFilters,
272
275
  TransactionAccessor,
273
276
  } from './data-accessor.js';
277
+ // === Database Inventory (Saga T10281 / Epic T10282 / Task T10305 — SG-BRAIN-DB-RESILIENCE) ===
278
+ export type {
279
+ DbConcurrency,
280
+ DbInventoryEntry,
281
+ DbPrivacy,
282
+ DbRole,
283
+ DbTier,
284
+ } from './db-inventory.js';
285
+ export { DB_INVENTORY } from './db-inventory.js';
274
286
  // === Dependency Registry Contracts ===
275
287
  export type {
276
288
  DependencyCategory,
@@ -313,8 +325,15 @@ export {
313
325
  } from './docs-taxonomy.js';
314
326
  // === Doctor: Worktree-Orphan Audit + Prune Types (T9790, T9808, T9962) ===
315
327
  // === Doctor: Saga Hierarchy Audit (T10119 — ADR-073 §1.2) ===
328
+ // === Doctor: DB-Substrate Survey (T10307 — Saga T10281 / Epic T10282) ===
316
329
  export type {
317
330
  ComprehensiveAuditResult,
331
+ DbSubstrateAuditResult,
332
+ DbSubstrateEntry,
333
+ DbSubstrateProjectSurvey,
334
+ DbSubstrateSummary,
335
+ DbSubstrateWarning,
336
+ DbSubstrateWarningKind,
318
337
  OrphanEntry,
319
338
  OrphanScanResult,
320
339
  PruneAuditEntry,
@@ -24,7 +24,7 @@ import type { TaskAnalysisResult, TaskRef } from '../results.js';
24
24
  * Common task types (API contract — matches CLI src/types/task.ts)
25
25
  */
26
26
  import type { TaskStatus } from '../status-registry.js';
27
- import type { TaskPriority } from '../task.js';
27
+ import type { TaskPriority, TaskType } from '../task.js';
28
28
  import type { TaskRecord } from '../task-record.js';
29
29
  import type { ExternalTask, ExternalTaskLink, ReconcileResult } from '../task-sync.js';
30
30
  import type {
@@ -725,7 +725,7 @@ export interface TasksAddBatchParams {
725
725
  depends?: string[];
726
726
  priority?: string;
727
727
  labels?: string[];
728
- type?: string; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944
728
+ type?: TaskType; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(saga|epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944 // ssot-exempt-ok: pre-existing exempt, narrowed string→TaskType (T10328)
729
729
  acceptance?: string[];
730
730
  phase?: string;
731
731
  size?: string;
@@ -768,7 +768,7 @@ export interface TasksAddParams {
768
768
  depends?: string[];
769
769
  priority?: string;
770
770
  labels?: string[];
771
- type?: string; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944
771
+ type?: TaskType; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(saga|epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944 // ssot-exempt-ok: pre-existing exempt, narrowed string→TaskType (T10328)
772
772
  acceptance?: string[];
773
773
  phase?: string;
774
774
  size?: string;
@@ -824,7 +824,7 @@ export interface TasksUpdateQueryParams {
824
824
  acceptance?: string[];
825
825
  /** Canonical wire field for parent task ID. @see ADR-057 D2 */
826
826
  parent?: string | null;
827
- type?: string; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944
827
+ type?: TaskType; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(saga|epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944 // ssot-exempt-ok: pre-existing exempt, narrowed string→TaskType (T10328)
828
828
  size?: string;
829
829
  files?: string[];
830
830
  /** Add files incrementally (mirrors --add-labels pattern). @task T9242 */
package/src/task.ts CHANGED
@@ -41,8 +41,19 @@ export type AcceptanceItem = string | AcceptanceGate;
41
41
  /** Task priority levels. */
42
42
  export type TaskPriority = 'critical' | 'high' | 'medium' | 'low';
43
43
 
44
- /** Task type in hierarchy. */
45
- export type TaskType = 'epic' | 'task' | 'subtask';
44
+ /**
45
+ * Task type in hierarchy.
46
+ *
47
+ * Per ADR-083 §2.5, `'saga'` is a first-class tier discriminator above
48
+ * Epic — a Saga groups N Epics across multiple releases. Hierarchy is now
49
+ * Saga (depth 0) → Epic (1) → Task (2) → Subtask (3), with `maxDepth=3`
50
+ * preserved under each Saga member.
51
+ *
52
+ * @adr ADR-083 §2.5
53
+ * @saga T10326
54
+ * @task T10328
55
+ */
56
+ export type TaskType = 'saga' | 'epic' | 'task' | 'subtask';
46
57
 
47
58
  /**
48
59
  * Task kind axis — orthogonal to {@link TaskType}, describes the intent of work.