@cleocode/contracts 2026.6.7 → 2026.6.8

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.
@@ -142,3 +142,68 @@ export interface BackupRecoverResult {
142
142
  */
143
143
  dryRun: boolean;
144
144
  }
145
+
146
+ /**
147
+ * Per-role outcome of a `cleo doctor repair` pass (T11829 · DHQ-060).
148
+ *
149
+ * `doctor repair` first PROBES each role's live DB via `PRAGMA quick_check`
150
+ * (the same probe the recovery pipeline runs on snapshot candidates) and only
151
+ * invokes the {@link recoverMalformedDb} → quarantine → restore-from-snapshot →
152
+ * verify pipeline for roles that are actually malformed. This record captures
153
+ * what was observed and done for one role.
154
+ *
155
+ * @task T11829
156
+ * @epic T11833
157
+ * @saga T11242
158
+ * @public
159
+ */
160
+ export interface DoctorRepairRoleResult {
161
+ /** Canonical role inspected. */
162
+ role: DbRole;
163
+ /** Absolute path to the role's live DB file (resolved from `DB_INVENTORY`). */
164
+ dbPath: string;
165
+ /** `true` when the live DB file exists on disk. */
166
+ present: boolean;
167
+ /**
168
+ * `true` when the live DB passed `PRAGMA quick_check` (or was absent) — i.e.
169
+ * NO repair was needed. `false` when corruption was detected.
170
+ */
171
+ healthy: boolean;
172
+ /**
173
+ * `'skipped'` — healthy or absent, nothing done;
174
+ * `'would-repair'` — `--dry-run`, corruption detected, repair NOT performed;
175
+ * `'repaired'` — corruption detected and snapshot restore succeeded;
176
+ * `'failed'` — corruption detected but recovery could not complete.
177
+ */
178
+ action: 'skipped' | 'would-repair' | 'repaired' | 'failed';
179
+ /** Absolute path to the snapshot restored (or that would be), when applicable. */
180
+ restoredFrom: string | null;
181
+ /** Absolute path to the quarantined corrupt DB, when a repair was performed. */
182
+ quarantinedTo: string | null;
183
+ /** Approximate data-loss window in hours, when a snapshot timestamp was parsed. */
184
+ dataLossWindowHours: number | null;
185
+ /** Human-readable detail (probe outcome, error reason, or restore summary). */
186
+ detail: string;
187
+ }
188
+
189
+ /**
190
+ * Aggregate result of a `cleo doctor repair` invocation across one or more roles
191
+ * (T11829 · DHQ-060).
192
+ *
193
+ * @task T11829
194
+ * @epic T11833
195
+ * @saga T11242
196
+ * @public
197
+ */
198
+ export interface DoctorRepairResult {
199
+ /** `true` when the run was a `--dry-run` (no files mutated). */
200
+ dryRun: boolean;
201
+ /** Per-role outcomes. */
202
+ roles: DoctorRepairRoleResult[];
203
+ /** Count of roles found malformed (`!healthy`). */
204
+ malformedCount: number;
205
+ /** Count of roles actually repaired this run. */
206
+ repairedCount: number;
207
+ /** Count of roles where a needed repair FAILED. */
208
+ failedCount: number;
209
+ }
@@ -7117,7 +7117,7 @@ export const OPERATIONS: OperationDef[] = [
7117
7117
  domain: 'docs',
7118
7118
  operation: 'add',
7119
7119
  description:
7120
- 'docs.add (mutate) — attach a local file or URL to a CLEO owner entity (task, session, observation)',
7120
+ 'docs.add (mutate) — attach a local file, URL, or inline content to a CLEO owner entity (task, session, observation)',
7121
7121
  tier: 1,
7122
7122
  idempotent: false,
7123
7123
  sessionRequired: false,
@@ -7141,6 +7141,12 @@ export const OPERATIONS: OperationDef[] = [
7141
7141
  required: false,
7142
7142
  description: 'Remote URL to attach',
7143
7143
  },
7144
+ {
7145
+ name: 'content',
7146
+ type: 'string' as const,
7147
+ required: false,
7148
+ description: 'Inline document body (T10965); mutually exclusive with file/url',
7149
+ },
7144
7150
  {
7145
7151
  name: 'desc',
7146
7152
  type: 'string' as const,
@@ -74,6 +74,7 @@ export const PROVENANCE_NODE_KINDS = [
74
74
  * - `related-task` — child task is related (non-parent) to parent
75
75
  * - `linked-decision` — node references a BRAIN decision
76
76
  * - `derived-from` — node was derived from a prior artifact
77
+ * - `shares-topic` — two docs co-occur on a canonical topic slug (T11826)
77
78
  *
78
79
  * @task T10166
79
80
  */
@@ -83,7 +84,8 @@ export type ProvenanceEdgeRelation =
83
84
  | 'superseded-by'
84
85
  | 'related-task'
85
86
  | 'linked-decision'
86
- | 'derived-from';
87
+ | 'derived-from'
88
+ | 'shares-topic';
87
89
 
88
90
  /**
89
91
  * Frozen const array backing {@link ProvenanceEdgeRelation} — used by the
@@ -98,6 +100,7 @@ export const PROVENANCE_EDGE_RELATIONS = [
98
100
  'related-task',
99
101
  'linked-decision',
100
102
  'derived-from',
103
+ 'shares-topic',
101
104
  ] as const satisfies ReadonlyArray<ProvenanceEdgeRelation>;
102
105
 
103
106
  /**
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `docs.read` core-SDK contract — the envelope-typed surface an external
3
+ * consumer (the Obsidian plugin, `cleo docs view`) calls to render a single
4
+ * CLEO doc directly from `cleo.db`, the SOLE doc authority (saga T11778).
5
+ *
6
+ * The response carries the doc **body** plus its full provenance **frontmatter**
7
+ * (slug, version anchors, supersession pointers, related tasks, topics). Binary
8
+ * or non-UTF-8 bodies are surfaced as base64 (T11825 AC2) so a consumer can
9
+ * render images/PDFs/files stored as blobs without a second round-trip.
10
+ *
11
+ * Part of the T10400 core-SDK API standard — this is a typed contract, not an
12
+ * ad-hoc shape (T11825 AC3).
13
+ *
14
+ * @task T11825 (Epic T11781 / Saga T11778)
15
+ * @adr ADR-078 — Docs SSoT as provenance graph
16
+ */
17
+
18
+ import { z } from 'zod';
19
+
20
+ /**
21
+ * Provenance frontmatter for a doc, sourced from the `attachments` columns that
22
+ * back the docs SSoT. Mirrors what a published doc's YAML frontmatter would
23
+ * carry, but read live from the DB rather than a file.
24
+ *
25
+ * @task T11825
26
+ */
27
+ export interface DocFrontmatter {
28
+ /** Stable human-readable slug (`attachments.slug`). */
29
+ readonly slug: string;
30
+ /** Doc-kind taxonomy tag (e.g. `adr`, `spec`), or null for untyped blobs. */
31
+ readonly kind: string | null;
32
+ /** Display title (falls back to slug). */
33
+ readonly title: string | null;
34
+ /** One-sentence human summary, distinct from the body. */
35
+ readonly summary: string | null;
36
+ /** Document workflow state (`draft | active | superseded | …`). */
37
+ readonly lifecycleStatus: string;
38
+ /** Sequential doc-version counter for this slug (`attachments.doc_version`). */
39
+ readonly docVersion: number;
40
+ /** CLEO release version that wrote this row (`attachments.owner_version`). */
41
+ readonly ownerVersion: string | null;
42
+ /** Slug of the doc this one supersedes, or null. */
43
+ readonly supersedes: string | null;
44
+ /** Slug of the doc that supersedes this one, or null. */
45
+ readonly supersededBy: string | null;
46
+ /** Canonical topic slugs this doc carries (`attachments.topics`). */
47
+ readonly topics: readonly string[];
48
+ /** `T####` task ids this doc relates to (`attachments.related_tasks`). */
49
+ readonly relatedTasks: readonly string[];
50
+ /** SHA-256 content address of the body. */
51
+ readonly sha256: string;
52
+ /** ISO-8601 creation instant. */
53
+ readonly createdAt: string;
54
+ }
55
+
56
+ /**
57
+ * The body payload of a doc read.
58
+ *
59
+ * Exactly one of {@link text} / {@link base64} is populated: UTF-8-decodable
60
+ * content is surfaced as `text` with `encoding: 'utf-8'`; binary content is
61
+ * surfaced as `base64` with `encoding: 'base64'`.
62
+ *
63
+ * @task T11825
64
+ */
65
+ export interface DocBody {
66
+ /** How {@link DocReadResponse.body} content is encoded. */
67
+ readonly encoding: 'utf-8' | 'base64';
68
+ /** UTF-8 body text, present when `encoding === 'utf-8'`. */
69
+ readonly text?: string;
70
+ /** Base64-encoded body bytes, present when `encoding === 'base64'`. */
71
+ readonly base64?: string;
72
+ /** Decoded byte length of the body. */
73
+ readonly sizeBytes: number;
74
+ /** IANA MIME type, when known. */
75
+ readonly mimeType: string | null;
76
+ }
77
+
78
+ /**
79
+ * The full `docs.read` response envelope payload.
80
+ *
81
+ * @task T11825
82
+ */
83
+ export interface DocReadResponse {
84
+ /** Provenance frontmatter. */
85
+ readonly frontmatter: DocFrontmatter;
86
+ /** Rendered body (text or base64). */
87
+ readonly body: DocBody;
88
+ }
89
+
90
+ /** Zod schema for {@link DocFrontmatter}. */
91
+ export const docFrontmatterSchema = z.object({
92
+ slug: z.string(),
93
+ kind: z.string().nullable(),
94
+ title: z.string().nullable(),
95
+ summary: z.string().nullable(),
96
+ lifecycleStatus: z.string(),
97
+ docVersion: z.number().int(),
98
+ ownerVersion: z.string().nullable(),
99
+ supersedes: z.string().nullable(),
100
+ supersededBy: z.string().nullable(),
101
+ topics: z.array(z.string()).readonly(),
102
+ relatedTasks: z.array(z.string()).readonly(),
103
+ sha256: z.string(),
104
+ createdAt: z.string(),
105
+ });
106
+
107
+ /** Zod schema for {@link DocBody}. */
108
+ export const docBodySchema = z.object({
109
+ encoding: z.enum(['utf-8', 'base64']),
110
+ text: z.string().optional(),
111
+ base64: z.string().optional(),
112
+ sizeBytes: z.number().int().nonnegative(),
113
+ mimeType: z.string().nullable(),
114
+ });
115
+
116
+ /** Zod schema for {@link DocReadResponse}. */
117
+ export const docReadResponseSchema = z.object({
118
+ frontmatter: docFrontmatterSchema,
119
+ body: docBodySchema,
120
+ });
121
+
122
+ /**
123
+ * Type guard for {@link DocReadResponse}.
124
+ *
125
+ * @param value - Unknown value to validate.
126
+ * @returns True when `value` conforms to the `docs.read` response schema.
127
+ * @task T11825
128
+ */
129
+ export function isDocReadResponse(value: unknown): value is DocReadResponse {
130
+ return docReadResponseSchema.safeParse(value).success;
131
+ }
package/src/index.ts CHANGED
@@ -346,6 +346,8 @@ export type {
346
346
  BackupRecoverResult,
347
347
  DbRecoveredRowCounts,
348
348
  DbRecoveryResult,
349
+ DoctorRepairResult,
350
+ DoctorRepairRoleResult,
349
351
  } from './db-recovery.js';
350
352
  // === Dependency Registry Contracts ===
351
353
  export type {
@@ -398,6 +400,14 @@ export {
398
400
  provenanceSessionNodeSchema,
399
401
  provenanceTaskNodeSchema,
400
402
  } from './docs/provenance.js';
403
+ // === docs.read core-SDK Contract (T11825) ===
404
+ export type { DocBody, DocFrontmatter, DocReadResponse } from './docs/read.js';
405
+ export {
406
+ docBodySchema,
407
+ docFrontmatterSchema,
408
+ docReadResponseSchema,
409
+ isDocReadResponse,
410
+ } from './docs/read.js';
401
411
  // === DocsAccessor Contracts (T9063) ===
402
412
  export type {
403
413
  DocExportFormat,
@@ -383,15 +383,25 @@ export interface DocsGenerateResult {
383
383
  /**
384
384
  * Parameters for `docs.add`.
385
385
  *
386
- * Exactly one of `file` or `url` must be provided.
386
+ * Exactly one of `file`, `url`, or `content` must be provided.
387
387
  */
388
388
  export interface DocsAddParams {
389
389
  /** Owner entity ID (task, session, observation, etc.) — required. */
390
390
  ownerId: string;
391
- /** Local file path to attach (mutually exclusive with `url`). */
391
+ /** Local file path to attach (mutually exclusive with `url` and `content`). */
392
392
  file?: string;
393
- /** URL to attach as reference (mutually exclusive with `file`). */
393
+ /** URL to attach as reference (mutually exclusive with `file` and `content`). */
394
394
  url?: string;
395
+ /**
396
+ * Inline document body authored directly on the command line or piped via
397
+ * stdin (`--content -`). Mutually exclusive with `file` and `url`. Stored
398
+ * as a `text/markdown` blob exactly as a file-sourced add would be —
399
+ * including the manifest.db mirror, slug reservation, memory observation,
400
+ * and audit trail — so a doc can be created without a pre-existing file.
401
+ *
402
+ * @task T10965
403
+ */
404
+ content?: string;
395
405
  /** Optional human-readable description. */
396
406
  desc?: string;
397
407
  /** Optional comma-separated labels for categorization. */