@cleocode/contracts 2026.5.111 → 2026.5.113

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 (118) hide show
  1. package/dist/__tests__/cli-meta-suggested-next.test.d.ts +21 -0
  2. package/dist/__tests__/cli-meta-suggested-next.test.d.ts.map +1 -0
  3. package/dist/__tests__/cli-meta-suggested-next.test.js +70 -0
  4. package/dist/__tests__/cli-meta-suggested-next.test.js.map +1 -0
  5. package/dist/__tests__/evidence-atom-schema.test.d.ts +16 -0
  6. package/dist/__tests__/evidence-atom-schema.test.d.ts.map +1 -0
  7. package/dist/__tests__/evidence-atom-schema.test.js +394 -0
  8. package/dist/__tests__/evidence-atom-schema.test.js.map +1 -0
  9. package/dist/config/__tests__/manifest.test.d.ts +13 -0
  10. package/dist/config/__tests__/manifest.test.d.ts.map +1 -0
  11. package/dist/config/__tests__/manifest.test.js +143 -0
  12. package/dist/config/__tests__/manifest.test.js.map +1 -0
  13. package/dist/config/manifest.d.ts +158 -0
  14. package/dist/config/manifest.d.ts.map +1 -0
  15. package/dist/config/manifest.js +123 -0
  16. package/dist/config/manifest.js.map +1 -0
  17. package/dist/docs/__tests__/provenance.test.d.ts +14 -0
  18. package/dist/docs/__tests__/provenance.test.d.ts.map +1 -0
  19. package/dist/docs/__tests__/provenance.test.js +579 -0
  20. package/dist/docs/__tests__/provenance.test.js.map +1 -0
  21. package/dist/docs/provenance.d.ts +731 -0
  22. package/dist/docs/provenance.d.ts.map +1 -0
  23. package/dist/docs/provenance.js +258 -0
  24. package/dist/docs/provenance.js.map +1 -0
  25. package/dist/docs-taxonomy.d.ts +24 -0
  26. package/dist/docs-taxonomy.d.ts.map +1 -1
  27. package/dist/docs-taxonomy.js +27 -0
  28. package/dist/docs-taxonomy.js.map +1 -1
  29. package/dist/doctor.d.ts +101 -0
  30. package/dist/doctor.d.ts.map +1 -1
  31. package/dist/errors.d.ts +4 -0
  32. package/dist/errors.d.ts.map +1 -1
  33. package/dist/errors.js +4 -0
  34. package/dist/errors.js.map +1 -1
  35. package/dist/evidence-atom-schema.d.ts +407 -0
  36. package/dist/evidence-atom-schema.d.ts.map +1 -0
  37. package/dist/evidence-atom-schema.js +505 -0
  38. package/dist/evidence-atom-schema.js.map +1 -0
  39. package/dist/exit-codes.d.ts +37 -0
  40. package/dist/exit-codes.d.ts.map +1 -1
  41. package/dist/exit-codes.js +37 -0
  42. package/dist/exit-codes.js.map +1 -1
  43. package/dist/index.d.ts +13 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +8 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/invariants/__tests__/adr-056-release.test.d.ts +16 -0
  48. package/dist/invariants/__tests__/adr-056-release.test.d.ts.map +1 -0
  49. package/dist/invariants/__tests__/adr-056-release.test.js +88 -0
  50. package/dist/invariants/__tests__/adr-056-release.test.js.map +1 -0
  51. package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts +22 -0
  52. package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts.map +1 -0
  53. package/dist/invariants/__tests__/adr-070-orchestration.test.js +151 -0
  54. package/dist/invariants/__tests__/adr-070-orchestration.test.js.map +1 -0
  55. package/dist/invariants/__tests__/registry.test.d.ts +14 -0
  56. package/dist/invariants/__tests__/registry.test.d.ts.map +1 -0
  57. package/dist/invariants/__tests__/registry.test.js +113 -0
  58. package/dist/invariants/__tests__/registry.test.js.map +1 -0
  59. package/dist/invariants/adr-056-release.d.ts +42 -0
  60. package/dist/invariants/adr-056-release.d.ts.map +1 -0
  61. package/dist/invariants/adr-056-release.js +143 -0
  62. package/dist/invariants/adr-056-release.js.map +1 -0
  63. package/dist/invariants/adr-070-orchestration.d.ts +73 -0
  64. package/dist/invariants/adr-070-orchestration.d.ts.map +1 -0
  65. package/dist/invariants/adr-070-orchestration.js +290 -0
  66. package/dist/invariants/adr-070-orchestration.js.map +1 -0
  67. package/dist/invariants/adr-073-saga.d.ts +32 -0
  68. package/dist/invariants/adr-073-saga.d.ts.map +1 -0
  69. package/dist/invariants/adr-073-saga.js +155 -0
  70. package/dist/invariants/adr-073-saga.js.map +1 -0
  71. package/dist/invariants/index.d.ts +162 -0
  72. package/dist/invariants/index.d.ts.map +1 -0
  73. package/dist/invariants/index.js +118 -0
  74. package/dist/invariants/index.js.map +1 -0
  75. package/dist/operations/__tests__/input-contract.test.d.ts +20 -0
  76. package/dist/operations/__tests__/input-contract.test.d.ts.map +1 -0
  77. package/dist/operations/__tests__/input-contract.test.js +158 -0
  78. package/dist/operations/__tests__/input-contract.test.js.map +1 -0
  79. package/dist/operations/docs.d.ts +16 -0
  80. package/dist/operations/docs.d.ts.map +1 -1
  81. package/dist/operations/input-contract.d.ts +267 -0
  82. package/dist/operations/input-contract.d.ts.map +1 -0
  83. package/dist/operations/input-contract.js +29 -0
  84. package/dist/operations/input-contract.js.map +1 -0
  85. package/dist/templates/__tests__/manifest.test.d.ts +18 -0
  86. package/dist/templates/__tests__/manifest.test.d.ts.map +1 -0
  87. package/dist/templates/__tests__/manifest.test.js +123 -0
  88. package/dist/templates/__tests__/manifest.test.js.map +1 -0
  89. package/dist/templates/manifest.d.ts +186 -0
  90. package/dist/templates/manifest.d.ts.map +1 -0
  91. package/dist/templates/manifest.js +165 -0
  92. package/dist/templates/manifest.js.map +1 -0
  93. package/package.json +2 -2
  94. package/src/__tests__/cli-meta-suggested-next.test.ts +76 -0
  95. package/src/__tests__/evidence-atom-schema.test.ts +499 -0
  96. package/src/config/__tests__/manifest.test.ts +176 -0
  97. package/src/config/manifest.ts +218 -0
  98. package/src/docs/__tests__/__snapshots__/provenance.test.ts.snap +252 -0
  99. package/src/docs/__tests__/provenance.test.ts +648 -0
  100. package/src/docs/provenance.ts +601 -0
  101. package/src/docs-taxonomy.ts +55 -0
  102. package/src/doctor.ts +111 -0
  103. package/src/errors.ts +4 -0
  104. package/src/evidence-atom-schema.ts +628 -0
  105. package/src/exit-codes.ts +39 -0
  106. package/src/index.ts +129 -0
  107. package/src/invariants/__tests__/adr-056-release.test.ts +102 -0
  108. package/src/invariants/__tests__/adr-070-orchestration.test.ts +186 -0
  109. package/src/invariants/__tests__/registry.test.ts +136 -0
  110. package/src/invariants/adr-056-release.ts +154 -0
  111. package/src/invariants/adr-070-orchestration.ts +317 -0
  112. package/src/invariants/adr-073-saga.ts +167 -0
  113. package/src/invariants/index.ts +222 -0
  114. package/src/operations/__tests__/input-contract.test.ts +188 -0
  115. package/src/operations/docs.ts +16 -0
  116. package/src/operations/input-contract.ts +301 -0
  117. package/src/templates/__tests__/manifest.test.ts +140 -0
  118. package/src/templates/manifest.ts +208 -0
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Docs Provenance Graph contract — cross-entity nodes + edges.
3
+ *
4
+ * Typed shape for the cross-entity provenance graph returned by C7
5
+ * (`cleo docs provenance` / `cleo docs graph`). A {@link DocProvenanceResponse}
6
+ * is the wire format consumed by the human renderer registered for the
7
+ * `(command='docs', kind='provenance')` slot — the same renderer family that
8
+ * consumes {@link TreeResponse} envelopes from E11.
9
+ *
10
+ * Nodes carry a `kind` discriminator (`doc` | `task` | `decision` | `session` |
11
+ * `memory`) so the renderer can pick the correct icon, colour, and detail
12
+ * layout per entity. Edges carry a `relation` discriminator that names the
13
+ * semantic link between two nodes (`attached-to`, `supersedes`,
14
+ * `superseded-by`, `related-task`, `linked-decision`, `derived-from`).
15
+ *
16
+ * This contract is intentionally read-only — every field is `readonly`, every
17
+ * array is `ReadonlyArray<…>` — so consumers cannot mutate the graph after
18
+ * receiving it from `cleo docs provenance`. Mutations flow through writer
19
+ * verbs (`cleo docs add`, `cleo docs supersede`, …) and emit a fresh graph on
20
+ * the next read.
21
+ *
22
+ * @see TreeResponse — E11 T10138 — the sibling envelope shape this contract
23
+ * composes with (a docs provenance UI may render the doc-subset as a tree).
24
+ * @see DocKindRegistry — packages/contracts/src/docs-taxonomy.ts — supplies
25
+ * the `kind` string carried on each {@link ProvenanceDocNode}.
26
+ * @see ADR-077 §3 — Human Render Contract (envelope kind discriminator).
27
+ * @see ADR-078 — Docs Provenance Graph (cross-entity edge taxonomy).
28
+ *
29
+ * @epic T10157 — Docs Provenance Graph (E12)
30
+ * @task T10166
31
+ */
32
+
33
+ import { z } from 'zod';
34
+
35
+ // ─── Node discriminator + edge relation enums ─────────────────────────────────
36
+
37
+ /**
38
+ * Discriminator for the entity kind a {@link ProvenanceNode} represents.
39
+ *
40
+ * - `doc` — a canonical document (ADR, spec, research note, …)
41
+ * - `task` — a CLEO task / epic / saga / subtask
42
+ * - `decision` — a BRAIN decision record
43
+ * - `session` — an agent session journal
44
+ * - `memory` — a BRAIN memory observation
45
+ *
46
+ * The string set is closed — extending it requires a new ADR and an additive
47
+ * migration so that older consumers fail-closed on unknown variants rather
48
+ * than silently dropping the node.
49
+ *
50
+ * @task T10166
51
+ */
52
+ export type ProvenanceNodeKind = 'doc' | 'task' | 'decision' | 'session' | 'memory';
53
+
54
+ /**
55
+ * Frozen const array backing {@link ProvenanceNodeKind} — used by the Zod
56
+ * enum schema and by exhaustiveness checks in switch statements.
57
+ *
58
+ * @task T10166
59
+ */
60
+ export const PROVENANCE_NODE_KINDS = [
61
+ 'doc',
62
+ 'task',
63
+ 'decision',
64
+ 'session',
65
+ 'memory',
66
+ ] as const satisfies ReadonlyArray<ProvenanceNodeKind>;
67
+
68
+ /**
69
+ * Semantic relationship between two provenance nodes.
70
+ *
71
+ * - `attached-to` — child is attached to parent (doc attached to task, …)
72
+ * - `supersedes` — child supersedes parent (newer doc replaces older)
73
+ * - `superseded-by` — child is superseded by parent (reverse of `supersedes`)
74
+ * - `related-task` — child task is related (non-parent) to parent
75
+ * - `linked-decision` — node references a BRAIN decision
76
+ * - `derived-from` — node was derived from a prior artifact
77
+ *
78
+ * @task T10166
79
+ */
80
+ export type ProvenanceEdgeRelation =
81
+ | 'attached-to'
82
+ | 'supersedes'
83
+ | 'superseded-by'
84
+ | 'related-task'
85
+ | 'linked-decision'
86
+ | 'derived-from';
87
+
88
+ /**
89
+ * Frozen const array backing {@link ProvenanceEdgeRelation} — used by the
90
+ * Zod enum schema and by exhaustiveness checks.
91
+ *
92
+ * @task T10166
93
+ */
94
+ export const PROVENANCE_EDGE_RELATIONS = [
95
+ 'attached-to',
96
+ 'supersedes',
97
+ 'superseded-by',
98
+ 'related-task',
99
+ 'linked-decision',
100
+ 'derived-from',
101
+ ] as const satisfies ReadonlyArray<ProvenanceEdgeRelation>;
102
+
103
+ /**
104
+ * Lifecycle status of a doc node — mirrors the canonical status union written
105
+ * by `cleo docs publish` / `cleo docs supersede`.
106
+ *
107
+ * - `active` — current canonical version
108
+ * - `superseded` — replaced by a newer doc (still readable for history)
109
+ * - `archived` — moved out of the active corpus (no successor)
110
+ * - `draft` — in-progress, not yet published
111
+ *
112
+ * @task T10166
113
+ */
114
+ export type DocLifecycleStatus = 'active' | 'superseded' | 'archived' | 'draft';
115
+
116
+ /**
117
+ * Frozen const array backing {@link DocLifecycleStatus}.
118
+ *
119
+ * @task T10166
120
+ */
121
+ export const DOC_LIFECYCLE_STATUSES = [
122
+ 'active',
123
+ 'superseded',
124
+ 'archived',
125
+ 'draft',
126
+ ] as const satisfies ReadonlyArray<DocLifecycleStatus>;
127
+
128
+ // ─── Node variants ────────────────────────────────────────────────────────────
129
+
130
+ /**
131
+ * Fields shared by every {@link ProvenanceNode} variant.
132
+ *
133
+ * `id` is the entity-scoped identifier (e.g. doc slug, task ID `T1234`,
134
+ * decision ID, session ID, memory ID). `id` is unique within a single
135
+ * {@link DocProvenanceResponse} but NOT globally across kinds — two nodes
136
+ * with the same `id` but different `kind` are distinct entities.
137
+ *
138
+ * `metadata` is an opaque, caller-defined key/value bag. Renderers MUST
139
+ * NOT assume any specific keys are present.
140
+ */
141
+ interface ProvenanceNodeBase {
142
+ /** Stable, entity-scoped identifier. */
143
+ readonly id: string;
144
+ /** Display title — short enough to render on a single line. */
145
+ readonly title: string;
146
+ /** Opaque key/value bag the renderer may surface verbatim. */
147
+ readonly metadata?: Readonly<Record<string, unknown>>;
148
+ }
149
+
150
+ /**
151
+ * Provenance node representing a canonical document (ADR, spec, research, …).
152
+ *
153
+ * `kind` is the user-facing document classification from
154
+ * {@link DocKindMetadata.kind} (e.g. `adr`, `spec`, `research`).
155
+ * `slug` is the canonical slug that addresses the doc through the docs SSoT.
156
+ *
157
+ * @task T10166
158
+ *
159
+ * @example
160
+ * ```ts
161
+ * const node: ProvenanceDocNode = {
162
+ * kind: 'doc',
163
+ * id: 'adr-078-docs-provenance-graph',
164
+ * slug: 'adr-078-docs-provenance-graph',
165
+ * docKind: 'adr',
166
+ * title: 'ADR 078 — Docs Provenance Graph',
167
+ * lifecycleStatus: 'active',
168
+ * publishedAt: '2026-05-22T18:00:00.000Z',
169
+ * };
170
+ * ```
171
+ */
172
+ export interface ProvenanceDocNode extends ProvenanceNodeBase {
173
+ readonly kind: 'doc';
174
+ /** Canonical doc slug. */
175
+ readonly slug: string;
176
+ /** User-facing document classification (DocKind from docs-taxonomy). */
177
+ readonly docKind: string;
178
+ /** Lifecycle state of this doc. */
179
+ readonly lifecycleStatus: DocLifecycleStatus;
180
+ /** ISO-8601 timestamp this doc was first published. */
181
+ readonly publishedAt: string;
182
+ /** ISO-8601 timestamp this doc was superseded — set iff `lifecycleStatus === 'superseded'`. */
183
+ readonly supersededAt?: string;
184
+ /** Optional short summary surfaced by the renderer. */
185
+ readonly summary?: string;
186
+ }
187
+
188
+ /**
189
+ * Provenance node representing a CLEO task (saga / epic / task / subtask).
190
+ *
191
+ * `taskType` mirrors the canonical 4-tier hierarchy from ADR-073 so the
192
+ * renderer can pick the correct icon (Σ / E / T / □) per tier.
193
+ *
194
+ * @task T10166
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * const node: ProvenanceTaskNode = {
199
+ * kind: 'task',
200
+ * id: 'T10166',
201
+ * title: 'C9: Provenance graph contract',
202
+ * taskType: 'task',
203
+ * status: 'in_progress',
204
+ * };
205
+ * ```
206
+ */
207
+ export interface ProvenanceTaskNode extends ProvenanceNodeBase {
208
+ readonly kind: 'task';
209
+ /** CLEO 4-tier task type. */
210
+ readonly taskType: 'saga' | 'epic' | 'task' | 'subtask';
211
+ /** Lifecycle status of the task. */
212
+ readonly status: 'pending' | 'in_progress' | 'done' | 'blocked' | 'cancelled' | 'archived';
213
+ }
214
+
215
+ /**
216
+ * Provenance node representing a BRAIN decision record.
217
+ *
218
+ * @task T10166
219
+ *
220
+ * @example
221
+ * ```ts
222
+ * const node: ProvenanceDecisionNode = {
223
+ * kind: 'decision',
224
+ * id: 'D-arch-001',
225
+ * title: 'Adopt evidence-based gate ritual',
226
+ * outcome: 'accepted',
227
+ * decidedAt: '2026-04-12T14:30:00.000Z',
228
+ * };
229
+ * ```
230
+ */
231
+ export interface ProvenanceDecisionNode extends ProvenanceNodeBase {
232
+ readonly kind: 'decision';
233
+ /** Decision lifecycle outcome. */
234
+ readonly outcome: 'proposed' | 'accepted' | 'rejected' | 'superseded';
235
+ /** ISO-8601 timestamp the decision was last transitioned. */
236
+ readonly decidedAt: string;
237
+ }
238
+
239
+ /**
240
+ * Provenance node representing an agent session journal.
241
+ *
242
+ * @task T10166
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * const node: ProvenanceSessionNode = {
247
+ * kind: 'session',
248
+ * id: 'ses_2026-05-22_abc123',
249
+ * title: 'Session 2026-05-22 — T10166 implementation',
250
+ * startedAt: '2026-05-22T18:00:00.000Z',
251
+ * };
252
+ * ```
253
+ */
254
+ export interface ProvenanceSessionNode extends ProvenanceNodeBase {
255
+ readonly kind: 'session';
256
+ /** ISO-8601 timestamp the session started. */
257
+ readonly startedAt: string;
258
+ /** ISO-8601 timestamp the session ended — undefined while still active. */
259
+ readonly endedAt?: string;
260
+ }
261
+
262
+ /**
263
+ * Provenance node representing a BRAIN memory observation.
264
+ *
265
+ * @task T10166
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * const node: ProvenanceMemoryNode = {
270
+ * kind: 'memory',
271
+ * id: 'O-2026-05-22-001',
272
+ * title: 'Observed: spawn worker contention under parallel sessions',
273
+ * memoryType: 'observation',
274
+ * recordedAt: '2026-05-22T19:15:00.000Z',
275
+ * };
276
+ * ```
277
+ */
278
+ export interface ProvenanceMemoryNode extends ProvenanceNodeBase {
279
+ readonly kind: 'memory';
280
+ /** BRAIN memory classification. */
281
+ readonly memoryType: 'observation' | 'pattern' | 'decision' | 'diary';
282
+ /** ISO-8601 timestamp the memory was recorded. */
283
+ readonly recordedAt: string;
284
+ }
285
+
286
+ /**
287
+ * Discriminated union of every provenance node variant.
288
+ *
289
+ * Switch on `node.kind` for exhaustive narrowing — the `default` branch will
290
+ * be statically reachable iff the discriminator set is extended without
291
+ * updating consumers, which is the desired fail-loud behaviour.
292
+ *
293
+ * @task T10166
294
+ */
295
+ export type ProvenanceNode =
296
+ | ProvenanceDocNode
297
+ | ProvenanceTaskNode
298
+ | ProvenanceDecisionNode
299
+ | ProvenanceSessionNode
300
+ | ProvenanceMemoryNode;
301
+
302
+ // ─── Edges ────────────────────────────────────────────────────────────────────
303
+
304
+ /**
305
+ * One semantic edge in the provenance graph.
306
+ *
307
+ * `from` and `to` are the `id` fields of two {@link ProvenanceNode} entries.
308
+ * Because `id` is only unique within a `kind`, edges also reference the
309
+ * source and target node kinds so the renderer can resolve them
310
+ * unambiguously.
311
+ *
312
+ * @task T10166
313
+ *
314
+ * @example
315
+ * ```ts
316
+ * const edge: ProvenanceEdge = {
317
+ * relation: 'attached-to',
318
+ * from: 'adr-078-docs-provenance-graph',
319
+ * fromKind: 'doc',
320
+ * to: 'T10157',
321
+ * toKind: 'task',
322
+ * addedAt: '2026-05-22T18:00:00.000Z',
323
+ * };
324
+ * ```
325
+ */
326
+ export interface ProvenanceEdge {
327
+ /** Semantic relation between {@link from} and {@link to}. */
328
+ readonly relation: ProvenanceEdgeRelation;
329
+ /** Source node `id`. */
330
+ readonly from: string;
331
+ /** Source node `kind` — required to disambiguate `id` across kinds. */
332
+ readonly fromKind: ProvenanceNodeKind;
333
+ /** Target node `id`. */
334
+ readonly to: string;
335
+ /** Target node `kind` — required to disambiguate `id` across kinds. */
336
+ readonly toKind: ProvenanceNodeKind;
337
+ /** ISO-8601 timestamp this edge was first recorded. */
338
+ readonly addedAt: string;
339
+ /** Optional one-line caption surfaced by the renderer. */
340
+ readonly summary?: string;
341
+ }
342
+
343
+ // ─── Top-level envelope ───────────────────────────────────────────────────────
344
+
345
+ /**
346
+ * Top-level wire envelope returned by `cleo docs provenance`.
347
+ *
348
+ * Carries a flat list of nodes + edges so the renderer (or a downstream
349
+ * consumer that wants to project the graph as a tree) can materialise the
350
+ * desired layout without an additional round trip.
351
+ *
352
+ * @task T10166
353
+ *
354
+ * @example
355
+ * ```ts
356
+ * const response: DocProvenanceResponse = {
357
+ * nodes: [
358
+ * {
359
+ * kind: 'doc',
360
+ * id: 'adr-078-docs-provenance-graph',
361
+ * slug: 'adr-078-docs-provenance-graph',
362
+ * docKind: 'adr',
363
+ * title: 'ADR 078',
364
+ * lifecycleStatus: 'active',
365
+ * publishedAt: '2026-05-22T18:00:00.000Z',
366
+ * },
367
+ * { kind: 'task', id: 'T10157', title: 'Epic E12', taskType: 'epic', status: 'in_progress' },
368
+ * ],
369
+ * edges: [
370
+ * {
371
+ * relation: 'attached-to',
372
+ * from: 'adr-078-docs-provenance-graph',
373
+ * fromKind: 'doc',
374
+ * to: 'T10157',
375
+ * toKind: 'task',
376
+ * addedAt: '2026-05-22T18:00:00.000Z',
377
+ * },
378
+ * ],
379
+ * totalNodes: 2,
380
+ * totalEdges: 1,
381
+ * };
382
+ * ```
383
+ */
384
+ export interface DocProvenanceResponse {
385
+ /** Flat list of nodes. Order is renderer-defined — typically pre-order traversal of the doc subset. */
386
+ readonly nodes: ReadonlyArray<ProvenanceNode>;
387
+ /** Flat list of edges between the nodes. */
388
+ readonly edges: ReadonlyArray<ProvenanceEdge>;
389
+ /** Total node count — MUST equal `nodes.length`. */
390
+ readonly totalNodes: number;
391
+ /** Total edge count — MUST equal `edges.length`. */
392
+ readonly totalEdges: number;
393
+ }
394
+
395
+ // ─── Type guards ──────────────────────────────────────────────────────────────
396
+
397
+ /**
398
+ * Type guard — narrows a {@link ProvenanceNode} to {@link ProvenanceDocNode}.
399
+ *
400
+ * @task T10166
401
+ *
402
+ * @example
403
+ * ```ts
404
+ * for (const node of response.nodes) {
405
+ * if (isProvenanceDocNode(node)) {
406
+ * console.log(`doc: ${node.slug} (${node.lifecycleStatus})`);
407
+ * }
408
+ * }
409
+ * ```
410
+ */
411
+ export function isProvenanceDocNode(node: ProvenanceNode): node is ProvenanceDocNode {
412
+ return node.kind === 'doc';
413
+ }
414
+
415
+ /**
416
+ * Type guard — narrows a {@link ProvenanceNode} to {@link ProvenanceTaskNode}.
417
+ *
418
+ * @task T10166
419
+ */
420
+ export function isProvenanceTaskNode(node: ProvenanceNode): node is ProvenanceTaskNode {
421
+ return node.kind === 'task';
422
+ }
423
+
424
+ /**
425
+ * Type guard — narrows a {@link ProvenanceNode} to {@link ProvenanceDecisionNode}.
426
+ *
427
+ * @task T10166
428
+ */
429
+ export function isProvenanceDecisionNode(node: ProvenanceNode): node is ProvenanceDecisionNode {
430
+ return node.kind === 'decision';
431
+ }
432
+
433
+ /**
434
+ * Type guard — narrows a {@link ProvenanceNode} to {@link ProvenanceSessionNode}.
435
+ *
436
+ * @task T10166
437
+ */
438
+ export function isProvenanceSessionNode(node: ProvenanceNode): node is ProvenanceSessionNode {
439
+ return node.kind === 'session';
440
+ }
441
+
442
+ /**
443
+ * Type guard — narrows a {@link ProvenanceNode} to {@link ProvenanceMemoryNode}.
444
+ *
445
+ * @task T10166
446
+ */
447
+ export function isProvenanceMemoryNode(node: ProvenanceNode): node is ProvenanceMemoryNode {
448
+ return node.kind === 'memory';
449
+ }
450
+
451
+ /**
452
+ * Runtime envelope guard for {@link DocProvenanceResponse}.
453
+ *
454
+ * Verifies the envelope shape without inspecting individual node payloads —
455
+ * use the Zod schema for deep validation.
456
+ *
457
+ * @task T10166
458
+ */
459
+ export function isDocProvenanceResponse(value: unknown): value is DocProvenanceResponse {
460
+ if (typeof value !== 'object' || value === null) return false;
461
+ const v = value as Record<string, unknown>;
462
+ return (
463
+ Array.isArray(v.nodes) &&
464
+ Array.isArray(v.edges) &&
465
+ typeof v.totalNodes === 'number' &&
466
+ typeof v.totalEdges === 'number'
467
+ );
468
+ }
469
+
470
+ // ─── Zod schemas ──────────────────────────────────────────────────────────────
471
+
472
+ /**
473
+ * Zod enum schema for {@link ProvenanceNodeKind}.
474
+ *
475
+ * @task T10166
476
+ */
477
+ export const provenanceNodeKindSchema = z.enum(PROVENANCE_NODE_KINDS);
478
+
479
+ /**
480
+ * Zod enum schema for {@link ProvenanceEdgeRelation}.
481
+ *
482
+ * @task T10166
483
+ */
484
+ export const provenanceEdgeRelationSchema = z.enum(PROVENANCE_EDGE_RELATIONS);
485
+
486
+ /**
487
+ * Zod enum schema for {@link DocLifecycleStatus}.
488
+ *
489
+ * @task T10166
490
+ */
491
+ export const docLifecycleStatusSchema = z.enum(DOC_LIFECYCLE_STATUSES);
492
+
493
+ const provenanceNodeBaseFields = {
494
+ id: z.string().min(1),
495
+ title: z.string().min(1),
496
+ metadata: z.record(z.string(), z.unknown()).optional(),
497
+ };
498
+
499
+ /**
500
+ * Zod schema for {@link ProvenanceDocNode}.
501
+ *
502
+ * @task T10166
503
+ */
504
+ export const provenanceDocNodeSchema = z.object({
505
+ ...provenanceNodeBaseFields,
506
+ kind: z.literal('doc'),
507
+ slug: z.string().min(1),
508
+ docKind: z.string().min(1),
509
+ lifecycleStatus: docLifecycleStatusSchema,
510
+ publishedAt: z.string().min(1),
511
+ supersededAt: z.string().min(1).optional(),
512
+ summary: z.string().optional(),
513
+ });
514
+
515
+ /**
516
+ * Zod schema for {@link ProvenanceTaskNode}.
517
+ *
518
+ * @task T10166
519
+ */
520
+ export const provenanceTaskNodeSchema = z.object({
521
+ ...provenanceNodeBaseFields,
522
+ kind: z.literal('task'),
523
+ taskType: z.enum(['saga', 'epic', 'task', 'subtask']),
524
+ status: z.enum(['pending', 'in_progress', 'done', 'blocked', 'cancelled', 'archived']),
525
+ });
526
+
527
+ /**
528
+ * Zod schema for {@link ProvenanceDecisionNode}.
529
+ *
530
+ * @task T10166
531
+ */
532
+ export const provenanceDecisionNodeSchema = z.object({
533
+ ...provenanceNodeBaseFields,
534
+ kind: z.literal('decision'),
535
+ outcome: z.enum(['proposed', 'accepted', 'rejected', 'superseded']),
536
+ decidedAt: z.string().min(1),
537
+ });
538
+
539
+ /**
540
+ * Zod schema for {@link ProvenanceSessionNode}.
541
+ *
542
+ * @task T10166
543
+ */
544
+ export const provenanceSessionNodeSchema = z.object({
545
+ ...provenanceNodeBaseFields,
546
+ kind: z.literal('session'),
547
+ startedAt: z.string().min(1),
548
+ endedAt: z.string().min(1).optional(),
549
+ });
550
+
551
+ /**
552
+ * Zod schema for {@link ProvenanceMemoryNode}.
553
+ *
554
+ * @task T10166
555
+ */
556
+ export const provenanceMemoryNodeSchema = z.object({
557
+ ...provenanceNodeBaseFields,
558
+ kind: z.literal('memory'),
559
+ memoryType: z.enum(['observation', 'pattern', 'decision', 'diary']),
560
+ recordedAt: z.string().min(1),
561
+ });
562
+
563
+ /**
564
+ * Discriminated-union schema for {@link ProvenanceNode}.
565
+ *
566
+ * @task T10166
567
+ */
568
+ export const provenanceNodeSchema = z.discriminatedUnion('kind', [
569
+ provenanceDocNodeSchema,
570
+ provenanceTaskNodeSchema,
571
+ provenanceDecisionNodeSchema,
572
+ provenanceSessionNodeSchema,
573
+ provenanceMemoryNodeSchema,
574
+ ]);
575
+
576
+ /**
577
+ * Zod schema for {@link ProvenanceEdge}.
578
+ *
579
+ * @task T10166
580
+ */
581
+ export const provenanceEdgeSchema = z.object({
582
+ relation: provenanceEdgeRelationSchema,
583
+ from: z.string().min(1),
584
+ fromKind: provenanceNodeKindSchema,
585
+ to: z.string().min(1),
586
+ toKind: provenanceNodeKindSchema,
587
+ addedAt: z.string().min(1),
588
+ summary: z.string().optional(),
589
+ });
590
+
591
+ /**
592
+ * Zod schema for {@link DocProvenanceResponse}.
593
+ *
594
+ * @task T10166
595
+ */
596
+ export const docProvenanceResponseSchema = z.object({
597
+ nodes: z.array(provenanceNodeSchema).readonly(),
598
+ edges: z.array(provenanceEdgeSchema).readonly(),
599
+ totalNodes: z.number().int().nonnegative(),
600
+ totalEdges: z.number().int().nonnegative(),
601
+ });