@cleocode/contracts 2026.5.122 → 2026.5.123

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 (108) hide show
  1. package/dist/__tests__/completion-operation-contracts.test.d.ts +8 -0
  2. package/dist/__tests__/completion-operation-contracts.test.d.ts.map +1 -0
  3. package/dist/__tests__/completion-operation-contracts.test.js +114 -0
  4. package/dist/__tests__/completion-operation-contracts.test.js.map +1 -0
  5. package/dist/__tests__/operation-envelope-validation.test.d.ts +8 -0
  6. package/dist/__tests__/operation-envelope-validation.test.d.ts.map +1 -0
  7. package/dist/__tests__/operation-envelope-validation.test.js +105 -0
  8. package/dist/__tests__/operation-envelope-validation.test.js.map +1 -0
  9. package/dist/__tests__/satisfies-atom.test.js +21 -3
  10. package/dist/__tests__/satisfies-atom.test.js.map +1 -1
  11. package/dist/__tests__/workgraph-context-scaffold-contracts.test.d.ts +8 -0
  12. package/dist/__tests__/workgraph-context-scaffold-contracts.test.d.ts.map +1 -0
  13. package/dist/__tests__/workgraph-context-scaffold-contracts.test.js +188 -0
  14. package/dist/__tests__/workgraph-context-scaffold-contracts.test.js.map +1 -0
  15. package/dist/__tests__/workgraph-operation-contracts.test.d.ts +8 -0
  16. package/dist/__tests__/workgraph-operation-contracts.test.d.ts.map +1 -0
  17. package/dist/__tests__/workgraph-operation-contracts.test.js +115 -0
  18. package/dist/__tests__/workgraph-operation-contracts.test.js.map +1 -0
  19. package/dist/data-accessor.d.ts +77 -0
  20. package/dist/data-accessor.d.ts.map +1 -1
  21. package/dist/data-accessor.js.map +1 -1
  22. package/dist/dispatch/__tests__/docs-update-registry.test.d.ts +2 -0
  23. package/dist/dispatch/__tests__/docs-update-registry.test.d.ts.map +1 -0
  24. package/dist/dispatch/__tests__/docs-update-registry.test.js +27 -0
  25. package/dist/dispatch/__tests__/docs-update-registry.test.js.map +1 -0
  26. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.d.ts +2 -0
  27. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.d.ts.map +1 -0
  28. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js +265 -0
  29. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js.map +1 -0
  30. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  31. package/dist/dispatch/operations-registry.js +487 -7
  32. package/dist/dispatch/operations-registry.js.map +1 -1
  33. package/dist/evidence-atom-schema.d.ts +4 -4
  34. package/dist/evidence-atom-schema.d.ts.map +1 -1
  35. package/dist/evidence-atom-schema.js +9 -9
  36. package/dist/evidence-atom-schema.js.map +1 -1
  37. package/dist/index.d.ts +12 -6
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +8 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/operation-envelope-validation.d.ts +79 -0
  42. package/dist/operation-envelope-validation.d.ts.map +1 -0
  43. package/dist/operation-envelope-validation.js +219 -0
  44. package/dist/operation-envelope-validation.js.map +1 -0
  45. package/dist/operations/__tests__/agent-operation-contract.test.d.ts +2 -0
  46. package/dist/operations/__tests__/agent-operation-contract.test.d.ts.map +1 -0
  47. package/dist/operations/__tests__/agent-operation-contract.test.js +128 -0
  48. package/dist/operations/__tests__/agent-operation-contract.test.js.map +1 -0
  49. package/dist/operations/__tests__/tasks-input-contracts.test.js +15 -0
  50. package/dist/operations/__tests__/tasks-input-contracts.test.js.map +1 -1
  51. package/dist/operations/agent-operation-contract.d.ts +227 -0
  52. package/dist/operations/agent-operation-contract.d.ts.map +1 -0
  53. package/dist/operations/agent-operation-contract.js +38 -0
  54. package/dist/operations/agent-operation-contract.js.map +1 -0
  55. package/dist/operations/docs.d.ts +19 -1
  56. package/dist/operations/docs.d.ts.map +1 -1
  57. package/dist/operations/docs.js +20 -0
  58. package/dist/operations/docs.js.map +1 -1
  59. package/dist/operations/focus.d.ts +1 -1
  60. package/dist/operations/index.d.ts +1 -0
  61. package/dist/operations/index.d.ts.map +1 -1
  62. package/dist/operations/index.js +1 -0
  63. package/dist/operations/index.js.map +1 -1
  64. package/dist/operations/orchestrate.d.ts +66 -0
  65. package/dist/operations/orchestrate.d.ts.map +1 -1
  66. package/dist/operations/tasks.d.ts +568 -11
  67. package/dist/operations/tasks.d.ts.map +1 -1
  68. package/dist/operations/tasks.js +22 -3
  69. package/dist/operations/tasks.js.map +1 -1
  70. package/dist/task-record.d.ts +15 -0
  71. package/dist/task-record.d.ts.map +1 -1
  72. package/dist/task.d.ts +15 -6
  73. package/dist/task.d.ts.map +1 -1
  74. package/dist/task.js.map +1 -1
  75. package/dist/tasks.d.ts +944 -0
  76. package/dist/tasks.d.ts.map +1 -1
  77. package/dist/tasks.js +198 -1
  78. package/dist/tasks.js.map +1 -1
  79. package/dist/workgraph.d.ts +1950 -0
  80. package/dist/workgraph.d.ts.map +1 -0
  81. package/dist/workgraph.js +449 -0
  82. package/dist/workgraph.js.map +1 -0
  83. package/package.json +10 -2
  84. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  85. package/src/__tests__/completion-operation-contracts.test.ts +154 -0
  86. package/src/__tests__/operation-envelope-validation.test.ts +140 -0
  87. package/src/__tests__/satisfies-atom.test.ts +23 -3
  88. package/src/__tests__/workgraph-context-scaffold-contracts.test.ts +232 -0
  89. package/src/__tests__/workgraph-operation-contracts.test.ts +151 -0
  90. package/src/data-accessor.ts +94 -1
  91. package/src/dispatch/__tests__/docs-update-registry.test.ts +35 -0
  92. package/src/dispatch/__tests__/operation-envelope-snapshots.test.ts +283 -0
  93. package/src/dispatch/operations-registry.ts +502 -7
  94. package/src/evidence-atom-schema.ts +10 -10
  95. package/src/index.ts +193 -2
  96. package/src/operation-envelope-validation.ts +319 -0
  97. package/src/operations/__tests__/agent-operation-contract.test.ts +161 -0
  98. package/src/operations/__tests__/tasks-input-contracts.test.ts +30 -0
  99. package/src/operations/agent-operation-contract.ts +253 -0
  100. package/src/operations/docs.ts +27 -7
  101. package/src/operations/focus.ts +1 -1
  102. package/src/operations/index.ts +1 -0
  103. package/src/operations/orchestrate.ts +71 -0
  104. package/src/operations/tasks.ts +612 -15
  105. package/src/task-record.ts +16 -0
  106. package/src/task.ts +16 -6
  107. package/src/tasks.ts +558 -0
  108. package/src/workgraph.ts +1153 -0
@@ -0,0 +1,1153 @@
1
+ /**
2
+ * Public WorkGraph contracts for PM-Core V2.
3
+ *
4
+ * The WorkGraph is the product boundary for task containment and non-containment
5
+ * relations. These contracts intentionally describe graph-shaped projections
6
+ * only; persistence, SQL tables, and CLI rendering stay behind core adapters.
7
+ *
8
+ * @task T10575
9
+ * @saga T10538
10
+ */
11
+ import { z } from 'zod';
12
+ import { TASK_RELATION_TYPES } from './enums.js';
13
+ import { TASK_STATUSES } from './status-registry.js';
14
+ import type { TaskPriority, TaskStatus, TaskType, VerificationGate } from './task.js';
15
+
16
+ /** Stable error code for WorkGraph parent/type matrix violations. */
17
+ export const E_WORKGRAPH_PARENT_TYPE_MATRIX = 'E_WORKGRAPH_PARENT_TYPE_MATRIX';
18
+
19
+ /** Stable relation categories exposed by the WorkGraph public API. */
20
+ export type WorkGraphRelationKind =
21
+ | 'contains'
22
+ | 'depends_on'
23
+ | 'blocks'
24
+ | 'relates_to'
25
+ | 'groups'
26
+ | 'satisfies';
27
+
28
+ /** Direction to traverse from a starting WorkGraph node. */
29
+ export type WorkGraphTraversalDirection = 'ancestors' | 'descendants' | 'upstream' | 'downstream';
30
+
31
+ /** Direction filter for direct non-containment edge reads around one root node. */
32
+ export type WorkGraphEdgeDirection = 'out' | 'in' | 'both';
33
+
34
+ /** Raw `task_relations.relation_type` values preserved for relation-edge callers. */
35
+ export type WorkGraphTaskRelationType = (typeof TASK_RELATION_TYPES)[number];
36
+
37
+ /** Stable identifier for a task-like WorkGraph vertex. */
38
+ export interface WorkGraphNodeRef {
39
+ /** Task, Saga, Epic, or Subtask ID. */
40
+ readonly id: string;
41
+ /** Hierarchy discriminator for this graph vertex. */
42
+ readonly type: TaskType;
43
+ }
44
+
45
+ /** Public task vertex shape returned by WorkGraph readers. */
46
+ export interface WorkGraphNode extends WorkGraphNodeRef {
47
+ /** Human-readable task title. */
48
+ readonly title: string;
49
+ /** Current lifecycle status of the task row. */
50
+ readonly status: TaskStatus;
51
+ /** Priority copied from the task row for queueing and display. */
52
+ readonly priority: TaskPriority;
53
+ /** Optional direct containment parent. Omitted for root nodes. */
54
+ readonly parentId?: string;
55
+ }
56
+
57
+ /** Directed edge between two WorkGraph nodes. */
58
+ export interface WorkGraphEdge {
59
+ /** Source task ID. */
60
+ readonly fromId: string;
61
+ /** Target task ID. */
62
+ readonly toId: string;
63
+ /** Public relation kind; storage-specific relation names are not exposed. */
64
+ readonly kind: WorkGraphRelationKind;
65
+ }
66
+
67
+ /** Storage source for semantically distinct WorkGraph edge projections. */
68
+ export type WorkGraphEdgeSource = 'relation' | 'dependency';
69
+
70
+ /** Non-containment relation edge backed by one `task_relations` row. */
71
+ export interface WorkGraphRelationEdge extends WorkGraphEdge {
72
+ /** Relation rows are advisory graph links, not scheduler blockers. */
73
+ readonly source: 'relation';
74
+ /** Raw relation type stored in `task_relations.relation_type`. */
75
+ readonly relationType: WorkGraphTaskRelationType;
76
+ /** Optional human-authored relation rationale from `task_relations.reason`. */
77
+ readonly reason?: string;
78
+ }
79
+
80
+ /** Scheduler dependency edge backed by one `task_dependencies` row. */
81
+ export interface WorkGraphDependencyEdge extends WorkGraphEdge {
82
+ /** Dependency rows drive scheduler blocking and are distinct from relation rows. */
83
+ readonly source: 'dependency';
84
+ /** Dependency edges always use the public dependency kind. */
85
+ readonly kind: 'depends_on';
86
+ }
87
+
88
+ /** Direct edge returned by the relation edge query service. */
89
+ export type WorkGraphDirectEdge = WorkGraphRelationEdge | WorkGraphDependencyEdge;
90
+
91
+ /** A fully materialized WorkGraph projection. */
92
+ export interface WorkGraphSnapshot {
93
+ /** Graph vertices keyed externally by `id`. */
94
+ readonly nodes: readonly WorkGraphNode[];
95
+ /** Directed graph edges between nodes. */
96
+ readonly edges: readonly WorkGraphEdge[];
97
+ }
98
+
99
+ /** Cursor/limit controls shared by WorkGraph paginated readers. */
100
+ export interface WorkGraphPaginationOptions {
101
+ /** Opaque cursor returned by the previous page. Omitted starts at the first row. */
102
+ readonly cursor?: string;
103
+ /** Maximum number of nodes to return. Implementations clamp invalid values. */
104
+ readonly limit?: number;
105
+ }
106
+
107
+ /** Page metadata returned by WorkGraph paginated readers. */
108
+ export interface WorkGraphPageInfo {
109
+ /** Cursor to pass to the next request when more rows exist. */
110
+ readonly nextCursor?: string;
111
+ /** True when additional rows are available after this page. */
112
+ readonly hasMore: boolean;
113
+ }
114
+
115
+ /** Options shared by WorkGraph traversal readers. */
116
+ export interface WorkGraphTraversalOptions extends WorkGraphPaginationOptions {
117
+ /** Starting node ID for the traversal. */
118
+ readonly rootId: string;
119
+ /** Direction to traverse from `rootId`. */
120
+ readonly direction: WorkGraphTraversalDirection;
121
+ /** Optional maximum edge depth; omitted means unbounded. */
122
+ readonly maxDepth?: number;
123
+ /** Include non-containment relation edges in addition to containment edges. */
124
+ readonly includeRelations?: boolean;
125
+ }
126
+
127
+ /** Paginated traversal projection with explicit page metadata. */
128
+ export interface WorkGraphTraversalResult extends WorkGraphSnapshot {
129
+ /** Requested traversal root. */
130
+ readonly rootId: string;
131
+ /** Direction traversed from the requested root. */
132
+ readonly direction: WorkGraphTraversalDirection;
133
+ /** Page metadata for cursor-based follow-up calls. */
134
+ readonly pageInfo: WorkGraphPageInfo;
135
+ }
136
+
137
+ /** Options for descendant tree readers. */
138
+ export interface WorkGraphTreeOptions extends WorkGraphPaginationOptions {
139
+ /** Starting parent ID whose descendants should be projected. */
140
+ readonly rootId: string;
141
+ /** Optional maximum descendant depth; omitted means unbounded. */
142
+ readonly maxDepth?: number;
143
+ }
144
+
145
+ /** Node returned by a paginated descendant tree projection. */
146
+ export interface WorkGraphTreeNode extends WorkGraphNode {
147
+ /** One-based edge depth from the requested root. */
148
+ readonly depth: number;
149
+ }
150
+
151
+ /** Paginated descendant tree projection. */
152
+ export interface WorkGraphTreeResult {
153
+ /** Requested tree root. */
154
+ readonly rootId: string;
155
+ /** Descendant nodes ordered breadth-first by depth, position, creation time, and ID. */
156
+ readonly nodes: readonly WorkGraphTreeNode[];
157
+ /** Containment edges connecting the returned nodes to the requested root or page peers. */
158
+ readonly edges: readonly WorkGraphEdge[];
159
+ /** Page metadata for cursor-based follow-up calls. */
160
+ readonly pageInfo: WorkGraphPageInfo;
161
+ }
162
+
163
+ /** Sparse status/type bucket counts returned by WorkGraph subtree rollups. */
164
+ export interface WorkGraphRollupCounts {
165
+ /** Total nodes counted for this bucket. */
166
+ readonly total: number;
167
+ /** Counts by canonical task status. Statuses with zero rows may be omitted. */
168
+ readonly byStatus: Partial<Record<TaskStatus, number>>;
169
+ /** Counts by canonical task type. Types with zero rows may be omitted. */
170
+ readonly byType: Partial<Record<TaskType, number>>;
171
+ }
172
+
173
+ /** Explicit denominator contract used for percentage fields in subtree summaries. */
174
+ export interface WorkGraphPercentDenominator {
175
+ /** Stable denominator basis. */
176
+ readonly basis: 'subtree-total';
177
+ /** Numeric denominator used to compute every percentage in the result. */
178
+ readonly total: number;
179
+ /** Human-readable rule statement for CLIs and APIs. */
180
+ readonly description: string;
181
+ }
182
+
183
+ /** Completion-style percentages derived from a subtree summary. */
184
+ export interface WorkGraphSubtreePercentages {
185
+ /** Done descendants divided by `percentDenominator.total`. */
186
+ readonly done: number;
187
+ /** Active descendants divided by `percentDenominator.total`. */
188
+ readonly active: number;
189
+ /** Blocked descendants divided by `percentDenominator.total`. */
190
+ readonly blocked: number;
191
+ /** Pending descendants divided by `percentDenominator.total`. */
192
+ readonly pending: number;
193
+ /** Cancelled descendants divided by `percentDenominator.total`. */
194
+ readonly cancelled: number;
195
+ }
196
+
197
+ /** Field-level mismatch proving a cached direct-child projection is stale. */
198
+ export interface WorkGraphProjectionMismatch {
199
+ /** Compared rollup field. */
200
+ readonly field: 'total' | `status:${TaskStatus}` | `type:${TaskType}`;
201
+ /** Count supplied by the caller's projection. */
202
+ readonly expected: number;
203
+ /** Count observed from current WorkGraph storage. */
204
+ readonly actual: number;
205
+ }
206
+
207
+ /** Options for storage-backed subtree summary and rollup reads. */
208
+ export interface WorkGraphSubtreeSummaryOptions {
209
+ /** Root node whose descendants are summarized. The root node itself is excluded. */
210
+ readonly rootId: string;
211
+ /** Optional direct-child projection to compare against current storage. */
212
+ readonly expectedDirectRollup?: WorkGraphRollupCounts;
213
+ }
214
+
215
+ /** Direct-child and full-subtree rollup summary for one WorkGraph root. */
216
+ export interface WorkGraphSubtreeSummaryResult {
217
+ /** Requested summary root. */
218
+ readonly rootId: string;
219
+ /** Counts for depth=1 descendants only. */
220
+ readonly direct: WorkGraphRollupCounts;
221
+ /** Counts for all descendants below the root. */
222
+ readonly subtree: WorkGraphRollupCounts;
223
+ /** Explicit denominator rules used by `percentages`. */
224
+ readonly percentDenominator: WorkGraphPercentDenominator;
225
+ /** Percentage values rounded to two decimals. */
226
+ readonly percentages: WorkGraphSubtreePercentages;
227
+ /** True when `expectedDirectRollup` disagrees with current storage. */
228
+ readonly staleProjection: boolean;
229
+ /** Field-level projection drift details. */
230
+ readonly projectionMismatches: readonly WorkGraphProjectionMismatch[];
231
+ }
232
+
233
+ /** Options for reading the ready frontier under a WorkGraph scope. */
234
+ export interface WorkGraphReadyFrontierOptions {
235
+ /** Root node whose descendant scope should be grouped by readiness. */
236
+ readonly rootId: string;
237
+ /** Optional SQL task-role/kind filter applied before grouping. */
238
+ readonly role?: string;
239
+ }
240
+
241
+ /** Dependency edge that keeps a frontier task from being runnable. */
242
+ export interface WorkGraphDependencyBlocker {
243
+ /** Upstream dependency task ID. */
244
+ readonly taskId: string;
245
+ /** Current upstream dependency status. */
246
+ readonly status: TaskStatus;
247
+ }
248
+
249
+ /** Verification gate that is not yet satisfied for a frontier task. */
250
+ export interface WorkGraphGateBlocker {
251
+ /** Gate name from the task verification state. */
252
+ readonly gate: VerificationGate;
253
+ }
254
+
255
+ /** Ready-frontier task with blocker metadata attached. */
256
+ export interface WorkGraphReadyFrontierTask extends WorkGraphNode {
257
+ /** SQL task-role/kind value copied from the task row when available. */
258
+ readonly role?: string;
259
+ /** Upstream dependencies whose status is not done/cancelled. */
260
+ readonly dependencyBlockers: readonly WorkGraphDependencyBlocker[];
261
+ /** Verification gates whose value is not true. */
262
+ readonly gateBlockers: readonly WorkGraphGateBlocker[];
263
+ }
264
+
265
+ /** Aggregate blocker -> task rollup for frontier diagnostics. */
266
+ export type WorkGraphReadyFrontierBlockedBy =
267
+ | {
268
+ /** Blocker category. */
269
+ readonly kind: 'dependency';
270
+ /** Dependency task ID blocking one or more frontier tasks. */
271
+ readonly blockerId: string;
272
+ /** Frontier task IDs blocked by this dependency. */
273
+ readonly blocks: readonly string[];
274
+ }
275
+ | {
276
+ /** Blocker category. */
277
+ readonly kind: 'gate';
278
+ /** Gate blocking one or more frontier tasks. */
279
+ readonly gate: VerificationGate;
280
+ /** Frontier task IDs blocked by this gate. */
281
+ readonly blocks: readonly string[];
282
+ };
283
+
284
+ /** Ready-frontier grouping result for a WorkGraph scope. */
285
+ export interface WorkGraphReadyFrontierResult {
286
+ /** Requested scope root. */
287
+ readonly rootId: string;
288
+ /** Optional role filter echoed from the request. */
289
+ readonly role?: string;
290
+ /** Grouped frontier rows. */
291
+ readonly groups: {
292
+ /** Tasks with no dependency blockers. */
293
+ readonly ready: readonly WorkGraphReadyFrontierTask[];
294
+ /** Tasks with one or more dependency blockers. */
295
+ readonly blocked: readonly WorkGraphReadyFrontierTask[];
296
+ /** Aggregate blocker rollup across both groups. */
297
+ readonly blockedBy: readonly WorkGraphReadyFrontierBlockedBy[];
298
+ };
299
+ }
300
+
301
+ /** Options for direct relation-edge reads around one WorkGraph node. */
302
+ export interface WorkGraphRelationEdgesOptions {
303
+ /** Root task ID used by the direction filter. */
304
+ readonly rootId: string;
305
+ /** Which side of the stored edge should match `rootId`. Defaults to `both`. */
306
+ readonly direction?: WorkGraphEdgeDirection;
307
+ /**
308
+ * Include `task_dependencies` edges in addition to `task_relations` rows.
309
+ * Dependency edges stay tagged as `source: 'dependency'` and never carry a
310
+ * relation reason.
311
+ */
312
+ readonly includeDependencies?: boolean;
313
+ }
314
+
315
+ /** Direct relation-edge read result with the normalized direction echoed. */
316
+ export interface WorkGraphRelationEdgesResult {
317
+ /** Requested root task ID. */
318
+ readonly rootId: string;
319
+ /** Direction filter applied by the reader. */
320
+ readonly direction: WorkGraphEdgeDirection;
321
+ /** Relation rows, plus dependency rows only when explicitly requested. */
322
+ readonly edges: readonly WorkGraphDirectEdge[];
323
+ }
324
+
325
+ /** Options for an aggregate WorkGraph audit read around one scope. */
326
+ export interface WorkGraphAuditOptions extends WorkGraphPaginationOptions {
327
+ /** Root task ID whose graph projections should be audited. */
328
+ readonly rootId: string;
329
+ /** Optional maximum descendant depth for traversal/tree projections. */
330
+ readonly maxDepth?: number;
331
+ /** Include direct relation-edge diagnostics. Defaults to false. */
332
+ readonly includeRelations?: boolean;
333
+ }
334
+
335
+ /** Aggregate WorkGraph audit result for CLI/API diagnostics. */
336
+ export interface WorkGraphAuditResult {
337
+ /** Requested audit root. */
338
+ readonly rootId: string;
339
+ /** Storage-agnostic hierarchy invariant result. */
340
+ readonly hierarchy: WorkGraphHierarchyValidationResult;
341
+ /** Descendant traversal projection used by the audit. */
342
+ readonly traversal: WorkGraphTraversalResult;
343
+ /** Runnable/blocked descendant frontier under the same root. */
344
+ readonly frontier: WorkGraphReadyFrontierResult;
345
+ /** Direct and subtree rollup summary under the same root. */
346
+ readonly rollup: WorkGraphSubtreeSummaryResult;
347
+ /** Optional direct relation/dependency edge projection when requested. */
348
+ readonly relationEdges?: WorkGraphRelationEdgesResult;
349
+ }
350
+
351
+ /** Reason a requested WorkGraph payload member was omitted from a bounded result. @task T10609 */
352
+ export type WorkGraphOmissionReason =
353
+ | 'budget_exceeded'
354
+ | 'not_requested'
355
+ | 'not_available'
356
+ | 'redacted'
357
+ | 'truncated';
358
+
359
+ /** Bounded context-accounting summary shared by WorkGraph context pack and scaffold calls. @task T10609 */
360
+ export interface WorkGraphContextBudget {
361
+ /** Caller-provided token budget for the response. */
362
+ readonly tokenBudget: number;
363
+ /** Estimated token usage of the returned payload. */
364
+ readonly estimatedTokens: number;
365
+ /** Remaining token budget after payload selection. */
366
+ readonly remainingTokens: number;
367
+ /** True when one or more requested payload members were omitted or truncated. */
368
+ readonly truncated: boolean;
369
+ }
370
+
371
+ /** Machine-readable omission emitted when bounded WorkGraph payloads drop data. @task T10609 */
372
+ export interface WorkGraphOmission {
373
+ /** Stable path/key of the omitted member. */
374
+ readonly path: string;
375
+ /** Why the member is absent from the result. */
376
+ readonly reason: WorkGraphOmissionReason;
377
+ /** Human-readable explanation for operators and prompt builders. */
378
+ readonly message: string;
379
+ /** Estimated tokens saved by omitting this member, when known. */
380
+ readonly estimatedTokens?: number;
381
+ }
382
+
383
+ /** Parameters for building a bounded WorkGraph context pack. @task T10609 */
384
+ export interface WorkGraphContextPackParams extends WorkGraphPaginationOptions {
385
+ /** Root task/saga/epic whose graph context should be packed. */
386
+ readonly rootId: string;
387
+ /** Maximum token budget for selected graph payloads. */
388
+ readonly tokenBudget?: number;
389
+ /** Include direct relation/dependency edges in the returned context. */
390
+ readonly includeRelations?: boolean;
391
+ /** Include readiness grouping under the requested scope. */
392
+ readonly includeReadiness?: boolean;
393
+ /** Include subtree rollup summary under the requested scope. */
394
+ readonly includeRollup?: boolean;
395
+ }
396
+
397
+ /** Bounded graph context pack for orchestration prompts and API consumers. @task T10609 */
398
+ export interface WorkGraphContextPack {
399
+ /** Requested graph scope root. */
400
+ readonly rootId: string;
401
+ /** ISO timestamp when the pack was generated. */
402
+ readonly generatedAt: string;
403
+ /** Budget accounting for the selected payload. */
404
+ readonly budget: WorkGraphContextBudget;
405
+ /** Hierarchy/tree slice selected for the pack. */
406
+ readonly slice: WorkGraphSlice;
407
+ /** Optional relation/dependency edges when requested and within budget. */
408
+ readonly relationEdges?: WorkGraphRelationEdgesResult;
409
+ /** Optional readiness grouping when requested and within budget. */
410
+ readonly readiness?: WorkGraphReadinessResult;
411
+ /** Optional subtree rollup when requested and within budget. */
412
+ readonly rollup?: WorkGraphSubtreeSummaryResult;
413
+ /** Omitted/truncated members with reasons. */
414
+ readonly omissions: readonly WorkGraphOmission[];
415
+ }
416
+
417
+ /** Parameters for reading a bounded graph slice around a root. @task T10609 */
418
+ export interface WorkGraphSliceParams extends WorkGraphPaginationOptions {
419
+ /** Root task/saga/epic whose slice should be projected. */
420
+ readonly rootId: string;
421
+ /** Traversal direction for the slice. */
422
+ readonly direction?: WorkGraphTraversalDirection;
423
+ /** Optional maximum edge depth for the slice. */
424
+ readonly maxDepth?: number;
425
+ /** Include direct relation/dependency edges adjacent to returned nodes. */
426
+ readonly includeRelations?: boolean;
427
+ }
428
+
429
+ /** Bounded graph slice that carries nodes, edges, and pagination metadata together. @task T10609 */
430
+ export interface WorkGraphSlice extends WorkGraphSnapshot {
431
+ /** Requested graph scope root. */
432
+ readonly rootId: string;
433
+ /** Direction used to materialize this slice. */
434
+ readonly direction: WorkGraphTraversalDirection;
435
+ /** Page metadata for cursor-based follow-up calls. */
436
+ readonly pageInfo: WorkGraphPageInfo;
437
+ /** Optional omission diagnostics when budget or pagination trims the slice. */
438
+ readonly omissions?: readonly WorkGraphOmission[];
439
+ }
440
+
441
+ /** Parameters for direct WorkGraph readiness checks. @task T10609 */
442
+ export interface WorkGraphReadinessParams {
443
+ /** Root task/saga/epic whose descendants should be checked. */
444
+ readonly rootId: string;
445
+ /** Optional SQL task-role/kind filter applied before grouping. */
446
+ readonly role?: string;
447
+ /** Include gate blockers in addition to dependency blockers. */
448
+ readonly includeGateBlockers?: boolean;
449
+ }
450
+
451
+ /** Contract-backed readiness result with an explicit ready boolean. @task T10609 */
452
+ export interface WorkGraphReadinessResult extends WorkGraphReadyFrontierResult {
453
+ /** True when at least one task is ready and no requested scope invariant failed. */
454
+ readonly ready: boolean;
455
+ /** Non-fatal readiness diagnostics. */
456
+ readonly warnings: readonly string[];
457
+ }
458
+
459
+ /** Parameters for validating WorkGraph scaffold inputs before applying them. @task T10609 */
460
+ export interface WorkGraphScaffoldValidateParams {
461
+ /** Root task/saga/epic that owns the scaffold proposal. */
462
+ readonly rootId: string;
463
+ /** Proposed nodes to create or reconcile. */
464
+ readonly nodes: readonly WorkGraphHierarchyInputNode[];
465
+ /** Proposed direct edges/relations to create or reconcile. */
466
+ readonly edges?: readonly WorkGraphDirectEdge[];
467
+ /** Preview validation only; echoed for callers that share validate/apply plumbing. */
468
+ readonly dryRun?: boolean;
469
+ }
470
+
471
+ /** Validation diagnostic for a WorkGraph scaffold proposal. @task T10609 */
472
+ export interface WorkGraphScaffoldValidationIssue {
473
+ /** Stable validation code. */
474
+ readonly code: string;
475
+ /** Human-readable validation message. */
476
+ readonly message: string;
477
+ /** Task or edge endpoint associated with the issue, when available. */
478
+ readonly taskId?: string;
479
+ /** Severity controls whether apply may proceed. */
480
+ readonly severity: 'error' | 'warning';
481
+ }
482
+
483
+ /** Result contract for WorkGraph scaffold validation. @task T10609 */
484
+ export interface WorkGraphScaffoldValidateResult {
485
+ /** Root task/saga/epic used for validation. */
486
+ readonly rootId: string;
487
+ /** True when no error-severity issues were found. */
488
+ readonly valid: boolean;
489
+ /** Echoes whether this was a dry-run validation. */
490
+ readonly dryRun: boolean;
491
+ /** Structured validation issues. */
492
+ readonly issues: readonly WorkGraphScaffoldValidationIssue[];
493
+ /** Hierarchy invariant result for the proposed nodes. */
494
+ readonly hierarchy: WorkGraphHierarchyValidationResult;
495
+ }
496
+
497
+ /** Parameters for applying a previously validated WorkGraph scaffold proposal. @task T10609 */
498
+ export interface WorkGraphScaffoldApplyParams extends WorkGraphScaffoldValidateParams {
499
+ /** Require callers to pass true to perform writes; false/omitted means preview. */
500
+ readonly apply?: boolean;
501
+ }
502
+
503
+ // ---------------------------------------------------------------------------
504
+ // Planning Doc Generator (T10634)
505
+ // ---------------------------------------------------------------------------
506
+
507
+ /** Target audience for planning doc output. @task T10634 */
508
+ export type WorkGraphAudienceMode = 'agent' | 'maintainer';
509
+
510
+ /** Parameters for generating a planning doc from WorkGraph data. @task T10634 */
511
+ export interface WorkGraphPlanningDocParams {
512
+ /** Root saga/epic whose graph should drive the planning doc. */
513
+ readonly rootId: string;
514
+ /** Audience mode — 'agent' for terse/LLM-optimized, 'maintainer' for human-readable. */
515
+ readonly audience: WorkGraphAudienceMode;
516
+ /** Optional token budget for agent-mode truncation. */
517
+ readonly tokenBudget?: number;
518
+ /** Include direct relation/dependency edges in the doc. */
519
+ readonly includeRelations?: boolean;
520
+ /** Include readiness grouping. */
521
+ readonly includeReadiness?: boolean;
522
+ /** Include rollup summary. */
523
+ readonly includeRollup?: boolean;
524
+ }
525
+
526
+ /** Planning doc generated from WorkGraph data. @task T10634 */
527
+ export interface WorkGraphPlanningDoc {
528
+ /** Requested root saga/epic ID. */
529
+ readonly rootId: string;
530
+ /** ISO timestamp when the doc was generated. */
531
+ readonly generatedAt: string;
532
+ /** Audience mode used for generation. */
533
+ readonly audience: WorkGraphAudienceMode;
534
+ /** Document title derived from the root task. */
535
+ readonly title: string;
536
+ /** Generated markdown content. */
537
+ readonly content: string;
538
+ /** Top-level section headings for TOC/navigation. */
539
+ readonly sections: readonly string[];
540
+ /** Estimated token count for the generated content. */
541
+ readonly estimatedTokens: number;
542
+ /** Budget accounting when tokenBudget is provided. */
543
+ readonly budget?: {
544
+ /** Caller-provided token budget. */
545
+ readonly tokenBudget: number;
546
+ /** Whether content was truncated to fit budget. */
547
+ readonly truncated: boolean;
548
+ };
549
+ }
550
+
551
+ /** Result contract for applying a WorkGraph scaffold proposal. @task T10609 */
552
+ export interface WorkGraphScaffoldApplyResult extends WorkGraphScaffoldValidateResult {
553
+ /** True when mutations were written. */
554
+ readonly applied: boolean;
555
+ /** Number of nodes created or reconciled. */
556
+ readonly nodesChanged: number;
557
+ /** Number of edges created or reconciled. */
558
+ readonly edgesChanged: number;
559
+ }
560
+
561
+ const taskTypeSchema = z.enum(['saga', 'epic', 'task', 'subtask']);
562
+ const taskPrioritySchema = z.enum(['critical', 'high', 'medium', 'low']);
563
+ const taskStatusSchema = z.enum(TASK_STATUSES);
564
+ const verificationGateSchema = z.enum([
565
+ 'implemented',
566
+ 'testsPassed',
567
+ 'qaPassed',
568
+ 'cleanupDone',
569
+ 'securityPassed',
570
+ 'documented',
571
+ 'nexusImpact',
572
+ ]);
573
+ const workGraphRelationKindSchema = z.enum([
574
+ 'contains',
575
+ 'depends_on',
576
+ 'blocks',
577
+ 'relates_to',
578
+ 'groups',
579
+ 'satisfies',
580
+ ]);
581
+ const workGraphTraversalDirectionSchema = z.enum([
582
+ 'ancestors',
583
+ 'descendants',
584
+ 'upstream',
585
+ 'downstream',
586
+ ]);
587
+ const workGraphEdgeDirectionSchema = z.enum(['out', 'in', 'both']);
588
+ const paginationParamsSchema = z.object({
589
+ cursor: z.string().min(1).optional(),
590
+ limit: z.number().int().positive().max(500).optional(),
591
+ });
592
+ const workGraphNodeSchema = z.object({
593
+ id: z.string().min(1),
594
+ type: taskTypeSchema,
595
+ title: z.string(),
596
+ status: taskStatusSchema,
597
+ priority: taskPrioritySchema,
598
+ parentId: z.string().min(1).optional(),
599
+ });
600
+ const workGraphEdgeSchema = z.object({
601
+ fromId: z.string().min(1),
602
+ toId: z.string().min(1),
603
+ kind: workGraphRelationKindSchema,
604
+ });
605
+ const workGraphHierarchyEdgeSchema = z
606
+ .object({
607
+ fromId: z.string().min(1),
608
+ toId: z.string().min(1),
609
+ kind: z.literal('contains'),
610
+ })
611
+ .strict();
612
+ const workGraphPageInfoSchema = z.object({
613
+ nextCursor: z.string().min(1).optional(),
614
+ hasMore: z.boolean(),
615
+ });
616
+ const workGraphRollupCountsSchema = z.object({
617
+ total: z.number().int().nonnegative(),
618
+ byStatus: z.partialRecord(taskStatusSchema, z.number().int().nonnegative()),
619
+ byType: z.partialRecord(taskTypeSchema, z.number().int().nonnegative()),
620
+ });
621
+ const workGraphSubtreePercentagesSchema = z.object({
622
+ done: z.number().nonnegative(),
623
+ active: z.number().nonnegative(),
624
+ blocked: z.number().nonnegative(),
625
+ pending: z.number().nonnegative(),
626
+ cancelled: z.number().nonnegative(),
627
+ });
628
+ const workGraphProjectionMismatchSchema = z.object({
629
+ field: z.string().min(1),
630
+ expected: z.number().int().nonnegative(),
631
+ actual: z.number().int().nonnegative(),
632
+ });
633
+ const workGraphReadyFrontierTaskSchema = workGraphNodeSchema.extend({
634
+ role: z.string().min(1).optional(),
635
+ dependencyBlockers: z.array(z.object({ taskId: z.string().min(1), status: taskStatusSchema })),
636
+ gateBlockers: z.array(z.object({ gate: verificationGateSchema })),
637
+ });
638
+ const workGraphRelationEdgeSchema = workGraphEdgeSchema.extend({
639
+ source: z.literal('relation'),
640
+ relationType: z.enum(TASK_RELATION_TYPES),
641
+ reason: z.string().min(1).optional(),
642
+ });
643
+ const workGraphDependencyEdgeSchema = workGraphEdgeSchema.extend({
644
+ source: z.literal('dependency'),
645
+ kind: z.literal('depends_on'),
646
+ });
647
+
648
+ const workGraphOmissionReasonSchema = z.enum([
649
+ 'budget_exceeded',
650
+ 'not_requested',
651
+ 'not_available',
652
+ 'redacted',
653
+ 'truncated',
654
+ ]);
655
+ const workGraphContextBudgetSchema = z.object({
656
+ tokenBudget: z.number().int().nonnegative(),
657
+ estimatedTokens: z.number().int().nonnegative(),
658
+ remainingTokens: z.number().int().nonnegative(),
659
+ truncated: z.boolean(),
660
+ });
661
+ const workGraphOmissionSchema = z.object({
662
+ path: z.string().min(1),
663
+ reason: workGraphOmissionReasonSchema,
664
+ message: z.string().min(1),
665
+ estimatedTokens: z.number().int().nonnegative().optional(),
666
+ });
667
+ const workGraphDirectEdgeSchema = z.discriminatedUnion('source', [
668
+ workGraphRelationEdgeSchema,
669
+ workGraphDependencyEdgeSchema,
670
+ ]);
671
+
672
+ /** Zod params for a bounded WorkGraph context pack. @task T10609 */
673
+ export const workGraphContextPackParamsSchema = paginationParamsSchema.extend({
674
+ rootId: z.string().min(1),
675
+ tokenBudget: z.number().int().positive().optional(),
676
+ includeRelations: z.boolean().optional(),
677
+ includeReadiness: z.boolean().optional(),
678
+ includeRollup: z.boolean().optional(),
679
+ });
680
+
681
+ /** Zod params for a bounded WorkGraph graph slice. @task T10609 */
682
+ export const workGraphSliceParamsSchema = paginationParamsSchema.extend({
683
+ rootId: z.string().min(1),
684
+ direction: workGraphTraversalDirectionSchema.optional(),
685
+ maxDepth: z.number().int().nonnegative().optional(),
686
+ includeRelations: z.boolean().optional(),
687
+ });
688
+
689
+ /** Zod result contract for a bounded WorkGraph graph slice. @task T10609 */
690
+ export const workGraphSliceSchema = z.object({
691
+ rootId: z.string().min(1),
692
+ direction: workGraphTraversalDirectionSchema,
693
+ nodes: z.array(workGraphNodeSchema),
694
+ edges: z.array(workGraphEdgeSchema),
695
+ pageInfo: workGraphPageInfoSchema,
696
+ omissions: z.array(workGraphOmissionSchema).optional(),
697
+ });
698
+
699
+ /** Zod params for direct WorkGraph readiness checks. @task T10609 */
700
+ export const workGraphReadinessParamsSchema = z.object({
701
+ rootId: z.string().min(1),
702
+ role: z.string().min(1).optional(),
703
+ includeGateBlockers: z.boolean().optional(),
704
+ });
705
+
706
+ /** Zod result contract for direct WorkGraph readiness checks. @task T10609 */
707
+ export const workGraphReadinessResultSchema = z.object({
708
+ rootId: z.string().min(1),
709
+ role: z.string().min(1).optional(),
710
+ ready: z.boolean(),
711
+ warnings: z.array(z.string()),
712
+ groups: z.object({
713
+ ready: z.array(workGraphReadyFrontierTaskSchema),
714
+ blocked: z.array(workGraphReadyFrontierTaskSchema),
715
+ blockedBy: z.array(
716
+ z.discriminatedUnion('kind', [
717
+ z.object({
718
+ kind: z.literal('dependency'),
719
+ blockerId: z.string().min(1),
720
+ blocks: z.array(z.string().min(1)),
721
+ }),
722
+ z.object({
723
+ kind: z.literal('gate'),
724
+ gate: verificationGateSchema,
725
+ blocks: z.array(z.string().min(1)),
726
+ }),
727
+ ]),
728
+ ),
729
+ }),
730
+ });
731
+
732
+ /** Zod result contract for a bounded WorkGraph context pack. @task T10609 */
733
+ export const workGraphContextPackSchema = z.object({
734
+ rootId: z.string().min(1),
735
+ generatedAt: z.string().min(1),
736
+ budget: workGraphContextBudgetSchema,
737
+ slice: workGraphSliceSchema,
738
+ relationEdges: z
739
+ .object({
740
+ rootId: z.string().min(1),
741
+ direction: workGraphEdgeDirectionSchema,
742
+ edges: z.array(workGraphDirectEdgeSchema),
743
+ })
744
+ .optional(),
745
+ readiness: workGraphReadinessResultSchema.optional(),
746
+ rollup: z.lazy(() => tasksRollupResultSchema).optional(),
747
+ omissions: z.array(workGraphOmissionSchema),
748
+ });
749
+
750
+ /** Zod params for WorkGraph scaffold validation. @task T10609 */
751
+ export const workGraphScaffoldValidateParamsSchema = z.object({
752
+ rootId: z.string().min(1),
753
+ nodes: z.array(
754
+ z.object({
755
+ id: z.string().min(1),
756
+ type: taskTypeSchema,
757
+ parentId: z.string().min(1).nullable().optional(),
758
+ }),
759
+ ),
760
+ edges: z.array(workGraphDirectEdgeSchema).optional(),
761
+ dryRun: z.boolean().optional(),
762
+ });
763
+
764
+ const workGraphScaffoldValidationIssueSchema = z.object({
765
+ code: z.string().min(1),
766
+ message: z.string().min(1),
767
+ taskId: z.string().min(1).optional(),
768
+ severity: z.enum(['error', 'warning']),
769
+ });
770
+
771
+ /** Zod result contract for WorkGraph scaffold validation. @task T10609 */
772
+ export const workGraphScaffoldValidateResultSchema = z.object({
773
+ rootId: z.string().min(1),
774
+ valid: z.boolean(),
775
+ dryRun: z.boolean(),
776
+ issues: z.array(workGraphScaffoldValidationIssueSchema),
777
+ hierarchy: z.object({
778
+ valid: z.boolean(),
779
+ violations: z.array(
780
+ z.object({
781
+ code: z.literal(E_WORKGRAPH_PARENT_TYPE_MATRIX),
782
+ taskId: z.string().min(1),
783
+ taskType: taskTypeSchema,
784
+ parentId: z.string().min(1).nullable(),
785
+ parentType: taskTypeSchema.optional(),
786
+ message: z.string().min(1),
787
+ }),
788
+ ),
789
+ }),
790
+ });
791
+
792
+ /** Zod params for WorkGraph scaffold apply. @task T10609 */
793
+ export const workGraphScaffoldApplyParamsSchema = workGraphScaffoldValidateParamsSchema.extend({
794
+ apply: z.boolean().optional(),
795
+ });
796
+
797
+ /** Zod result contract for WorkGraph scaffold apply. @task T10609 */
798
+ export const workGraphScaffoldApplyResultSchema = workGraphScaffoldValidateResultSchema.extend({
799
+ applied: z.boolean(),
800
+ nodesChanged: z.number().int().nonnegative(),
801
+ edgesChanged: z.number().int().nonnegative(),
802
+ });
803
+
804
+ /** Zod params for WorkGraph planning doc generation. @task T10634 */
805
+ export const workGraphPlanningDocParamsSchema = z.object({
806
+ rootId: z.string().min(1),
807
+ audience: z.enum(['agent', 'maintainer']),
808
+ tokenBudget: z.number().int().positive().optional(),
809
+ includeRelations: z.boolean().optional(),
810
+ includeReadiness: z.boolean().optional(),
811
+ includeRollup: z.boolean().optional(),
812
+ });
813
+
814
+ /** Zod result contract for WorkGraph planning doc. @task T10634 */
815
+ export const workGraphPlanningDocSchema = z.object({
816
+ rootId: z.string().min(1),
817
+ generatedAt: z.string().min(1),
818
+ audience: z.enum(['agent', 'maintainer']),
819
+ title: z.string().min(1),
820
+ content: z.string(),
821
+ sections: z.array(z.string().min(1)),
822
+ estimatedTokens: z.number().int().nonnegative(),
823
+ budget: z
824
+ .object({
825
+ tokenBudget: z.number().int().positive(),
826
+ truncated: z.boolean(),
827
+ })
828
+ .optional(),
829
+ });
830
+
831
+ /** Zod params for `tasks.traverse`. */
832
+ export const tasksTraverseParamsSchema = paginationParamsSchema.extend({
833
+ rootId: z.string().min(1),
834
+ direction: workGraphTraversalDirectionSchema,
835
+ maxDepth: z.number().int().nonnegative().optional(),
836
+ includeRelations: z.boolean().optional(),
837
+ });
838
+
839
+ /** Zod result contract for `tasks.traverse`. */
840
+ export const tasksTraverseResultSchema = z.object({
841
+ rootId: z.string().min(1),
842
+ direction: workGraphTraversalDirectionSchema,
843
+ nodes: z.array(workGraphNodeSchema),
844
+ edges: z.array(workGraphEdgeSchema),
845
+ pageInfo: workGraphPageInfoSchema,
846
+ });
847
+
848
+ /** Zod params for `tasks.tree`. */
849
+ export const tasksTreeParamsSchema = paginationParamsSchema.extend({
850
+ rootId: z.string().min(1),
851
+ maxDepth: z.number().int().nonnegative().optional(),
852
+ });
853
+
854
+ /** Zod result contract for `tasks.tree`. */
855
+ export const tasksTreeResultSchema = z.object({
856
+ rootId: z.string().min(1),
857
+ nodes: z.array(workGraphNodeSchema.extend({ depth: z.number().int().positive() })),
858
+ edges: z.array(workGraphHierarchyEdgeSchema),
859
+ pageInfo: workGraphPageInfoSchema,
860
+ });
861
+
862
+ /** Zod params for `tasks.rollup`. */
863
+ export const tasksRollupParamsSchema = z.object({
864
+ rootId: z.string().min(1),
865
+ expectedDirectRollup: workGraphRollupCountsSchema.optional(),
866
+ });
867
+
868
+ /** Zod result contract for `tasks.rollup`. */
869
+ export const tasksRollupResultSchema = z.object({
870
+ rootId: z.string().min(1),
871
+ direct: workGraphRollupCountsSchema,
872
+ subtree: workGraphRollupCountsSchema,
873
+ percentDenominator: z.object({
874
+ basis: z.literal('subtree-total'),
875
+ total: z.number().int().nonnegative(),
876
+ description: z.string().min(1),
877
+ }),
878
+ percentages: workGraphSubtreePercentagesSchema,
879
+ staleProjection: z.boolean(),
880
+ projectionMismatches: z.array(workGraphProjectionMismatchSchema),
881
+ });
882
+
883
+ /** Zod params for `tasks.frontier`. */
884
+ export const tasksFrontierParamsSchema = z.object({
885
+ rootId: z.string().min(1),
886
+ role: z.string().min(1).optional(),
887
+ });
888
+
889
+ /** Zod result contract for `tasks.frontier`. */
890
+ export const tasksFrontierResultSchema = z.object({
891
+ rootId: z.string().min(1),
892
+ role: z.string().min(1).optional(),
893
+ groups: z.object({
894
+ ready: z.array(workGraphReadyFrontierTaskSchema),
895
+ blocked: z.array(workGraphReadyFrontierTaskSchema),
896
+ blockedBy: z.array(
897
+ z.discriminatedUnion('kind', [
898
+ z.object({
899
+ kind: z.literal('dependency'),
900
+ blockerId: z.string().min(1),
901
+ blocks: z.array(z.string().min(1)),
902
+ }),
903
+ z.object({
904
+ kind: z.literal('gate'),
905
+ gate: verificationGateSchema,
906
+ blocks: z.array(z.string().min(1)),
907
+ }),
908
+ ]),
909
+ ),
910
+ }),
911
+ });
912
+
913
+ /** Zod params for `tasks.workgraph.audit`. */
914
+ export const tasksWorkGraphAuditParamsSchema = paginationParamsSchema.extend({
915
+ rootId: z.string().min(1),
916
+ maxDepth: z.number().int().nonnegative().optional(),
917
+ includeRelations: z.boolean().optional(),
918
+ });
919
+
920
+ /** Zod result contract for `tasks.workgraph.audit`. */
921
+ export const tasksWorkGraphAuditResultSchema = z.object({
922
+ rootId: z.string().min(1),
923
+ hierarchy: z.object({
924
+ valid: z.boolean(),
925
+ violations: z.array(
926
+ z.object({
927
+ code: z.literal(E_WORKGRAPH_PARENT_TYPE_MATRIX),
928
+ taskId: z.string().min(1),
929
+ taskType: taskTypeSchema,
930
+ parentId: z.string().min(1).nullable(),
931
+ parentType: taskTypeSchema.optional(),
932
+ message: z.string().min(1),
933
+ }),
934
+ ),
935
+ }),
936
+ traversal: tasksTraverseResultSchema,
937
+ frontier: tasksFrontierResultSchema,
938
+ rollup: tasksRollupResultSchema,
939
+ relationEdges: z
940
+ .object({
941
+ rootId: z.string().min(1),
942
+ direction: workGraphEdgeDirectionSchema,
943
+ edges: z.array(
944
+ z.discriminatedUnion('source', [
945
+ workGraphRelationEdgeSchema,
946
+ workGraphDependencyEdgeSchema,
947
+ ]),
948
+ ),
949
+ })
950
+ .optional(),
951
+ });
952
+
953
+ export type TasksTraverseParamsInput = z.input<typeof tasksTraverseParamsSchema>;
954
+ export type TasksTreeParamsInput = z.input<typeof tasksTreeParamsSchema>;
955
+ export type TasksRollupParamsInput = z.input<typeof tasksRollupParamsSchema>;
956
+ export type TasksFrontierParamsInput = z.input<typeof tasksFrontierParamsSchema>;
957
+ export type TasksWorkGraphAuditParamsInput = z.input<typeof tasksWorkGraphAuditParamsSchema>;
958
+
959
+ /** Minimal public reader facade for future WorkGraph implementations. */
960
+ export interface WorkGraphReader {
961
+ /** Read a graph snapshot for the current project context. */
962
+ snapshot(): Promise<WorkGraphSnapshot> | WorkGraphSnapshot;
963
+ /** Traverse from a root node using storage-hidden graph semantics. */
964
+ traverse(
965
+ options: WorkGraphTraversalOptions,
966
+ ): Promise<WorkGraphTraversalResult> | WorkGraphTraversalResult;
967
+ /** Read a paginated descendant tree rooted at a task-like node. */
968
+ tree(options: WorkGraphTreeOptions): Promise<WorkGraphTreeResult> | WorkGraphTreeResult;
969
+ /** Read direct and full-subtree rollup counts rooted at a task-like node. */
970
+ summarizeSubtree(
971
+ options: WorkGraphSubtreeSummaryOptions,
972
+ ): Promise<WorkGraphSubtreeSummaryResult> | WorkGraphSubtreeSummaryResult;
973
+ }
974
+
975
+ /** Task vertex returned by containment-only WorkGraph queries. */
976
+ export type WorkGraphContainmentNode = WorkGraphNode;
977
+
978
+ /** Ancestor chain for one requested WorkGraph root. */
979
+ export interface WorkGraphContainmentAncestorsResult {
980
+ /** Requested task ID. */
981
+ readonly rootId: string;
982
+ /** Ancestors ordered from hierarchy root down to direct parent. */
983
+ readonly ancestors: readonly WorkGraphContainmentNode[];
984
+ }
985
+
986
+ /** Direct children for one requested WorkGraph parent. */
987
+ export interface WorkGraphContainmentChildrenResult {
988
+ /** Requested parent task ID. */
989
+ readonly rootId: string;
990
+ /** Direct children ordered by task position/creation order. */
991
+ readonly children: readonly WorkGraphContainmentNode[];
992
+ }
993
+
994
+ /**
995
+ * Storage-backed containment lookup facade.
996
+ *
997
+ * Implementations MUST answer from `tasks.parent_id` only. They MUST NOT read
998
+ * `task_relations`, including `groups`, because Saga membership is not direct
999
+ * containment.
1000
+ */
1001
+ export interface WorkGraphContainmentQueryService {
1002
+ /** Load ancestor chains for many roots in one batched query. */
1003
+ getAncestors(rootIds: readonly string[]): readonly WorkGraphContainmentAncestorsResult[];
1004
+ /** Load direct children for many parents in one batched query. */
1005
+ getChildren(parentIds: readonly string[]): readonly WorkGraphContainmentChildrenResult[];
1006
+ /** Group dependency-ready and dependency-blocked descendants for one scope. */
1007
+ readyFrontier(options: WorkGraphReadyFrontierOptions): WorkGraphReadyFrontierResult;
1008
+ }
1009
+
1010
+ /** Storage-backed relation lookup facade for direct non-containment graph edges. */
1011
+ export interface WorkGraphRelationQueryService {
1012
+ /**
1013
+ * List direct edges around `rootId` using `task_relations` by default.
1014
+ * Implementations MUST NOT conflate scheduler dependencies with relation rows;
1015
+ * dependency edges are included only when explicitly requested and remain
1016
+ * tagged as `source: 'dependency'`.
1017
+ */
1018
+ listRelationEdges(options: WorkGraphRelationEdgesOptions): WorkGraphRelationEdgesResult;
1019
+ }
1020
+
1021
+ /** Minimal task hierarchy row accepted by the WorkGraph invariant validator. */
1022
+ export interface WorkGraphHierarchyInputNode {
1023
+ /** Stable task, saga, epic, or subtask identifier. */
1024
+ readonly id: string;
1025
+ /** Canonical hierarchy discriminator for the node. */
1026
+ readonly type: TaskType;
1027
+ /** Direct containment parent; absent/null means root. */
1028
+ readonly parentId?: string | null;
1029
+ }
1030
+
1031
+ /** Structured hierarchy invariant violation for CLI/API callers. */
1032
+ export interface WorkGraphHierarchyViolation {
1033
+ /** Stable machine-readable error code. */
1034
+ readonly code: typeof E_WORKGRAPH_PARENT_TYPE_MATRIX;
1035
+ /** Child node that violates the parent/type matrix. */
1036
+ readonly taskId: string;
1037
+ /** Child node type. */
1038
+ readonly taskType: TaskType;
1039
+ /** Parent ID when present; null for invalid root/non-root shape. */
1040
+ readonly parentId: string | null;
1041
+ /** Parent type when parent row is known. */
1042
+ readonly parentType?: TaskType;
1043
+ /** Human-readable remediation summary. */
1044
+ readonly message: string;
1045
+ }
1046
+
1047
+ /** Result returned by the WorkGraph hierarchy invariant validator. */
1048
+ export interface WorkGraphHierarchyValidationResult {
1049
+ /** True when every node satisfies the parent/type matrix. */
1050
+ readonly valid: boolean;
1051
+ /** Ordered list of matrix violations in input order. */
1052
+ readonly violations: readonly WorkGraphHierarchyViolation[];
1053
+ }
1054
+
1055
+ /** Options for WorkGraph hierarchy invariant validation. */
1056
+ export interface WorkGraphHierarchyValidationOptions {
1057
+ /** Throw on the first violation instead of returning all diagnostics. */
1058
+ readonly throwOnViolation?: boolean;
1059
+ }
1060
+
1061
+ const PARENT_TYPE_MATRIX: Readonly<Record<TaskType, readonly TaskType[]>> = {
1062
+ saga: [],
1063
+ epic: [],
1064
+ task: ['epic'],
1065
+ subtask: ['epic', 'task'],
1066
+ };
1067
+
1068
+ function describeAllowedParents(type: TaskType): string {
1069
+ const allowed = PARENT_TYPE_MATRIX[type];
1070
+ return allowed.length === 0 ? 'root only' : allowed.join('|');
1071
+ }
1072
+
1073
+ function formatHierarchyViolation(violation: WorkGraphHierarchyViolation): string {
1074
+ return `${violation.code}: task ${violation.taskId} type=${violation.taskType} parent=${violation.parentId ?? '<root>'} parentType=${violation.parentType ?? '<missing>'}; ${violation.message}`;
1075
+ }
1076
+
1077
+ /** Error thrown by fail-fast WorkGraph hierarchy validation. */
1078
+ export class WorkGraphHierarchyInvariantError extends Error {
1079
+ /** Stable machine-readable code for the invariant breach. */
1080
+ readonly code = E_WORKGRAPH_PARENT_TYPE_MATRIX;
1081
+
1082
+ /** Structured diagnostic for the first hierarchy invariant violation. */
1083
+ readonly violation: WorkGraphHierarchyViolation;
1084
+
1085
+ /**
1086
+ * Build a WorkGraph hierarchy invariant error from a structured violation.
1087
+ * @param violation - The first violation encountered by the validator.
1088
+ */
1089
+ constructor(violation: WorkGraphHierarchyViolation) {
1090
+ super(formatHierarchyViolation(violation));
1091
+ this.name = 'WorkGraphHierarchyInvariantError';
1092
+ this.violation = violation;
1093
+ }
1094
+ }
1095
+
1096
+ function makeViolation(
1097
+ node: WorkGraphHierarchyInputNode,
1098
+ parent: WorkGraphHierarchyInputNode | undefined,
1099
+ ): WorkGraphHierarchyViolation {
1100
+ const parentId = node.parentId ?? null;
1101
+ const parentType = parent?.type;
1102
+ const expected = describeAllowedParents(node.type);
1103
+ const actual = parentId === null ? 'root' : (parentType ?? 'missing parent');
1104
+ return {
1105
+ code: E_WORKGRAPH_PARENT_TYPE_MATRIX,
1106
+ taskId: node.id,
1107
+ taskType: node.type,
1108
+ parentId,
1109
+ parentType,
1110
+ message: `tasks.parent_id must follow saga/epic roots, epic->task|subtask, and task->subtask; expected ${node.type} parent ${expected}, got ${actual}`,
1111
+ };
1112
+ }
1113
+
1114
+ /**
1115
+ * Validate WorkGraph hierarchy rows against CLEO's canonical type/parent matrix.
1116
+ *
1117
+ * The validator mirrors the SQLite trigger invariant in a storage-agnostic form
1118
+ * for core adapters, projections, and API callers: sagas and epics are roots,
1119
+ * epics may contain tasks or subtasks, tasks may contain subtasks, and subtasks
1120
+ * are leaves. Saga membership remains a `groups` relation, never `parentId`.
1121
+ *
1122
+ * @param nodes - Task-like hierarchy rows to validate.
1123
+ * @param options - Optional fail-fast behavior for enforcement call sites.
1124
+ * @returns Structured validation result with all violations in input order.
1125
+ * @task T10576
1126
+ * @saga T10538
1127
+ */
1128
+ export function validateWorkGraphHierarchy(
1129
+ nodes: readonly WorkGraphHierarchyInputNode[],
1130
+ options: WorkGraphHierarchyValidationOptions = {},
1131
+ ): WorkGraphHierarchyValidationResult {
1132
+ const byId = new Map<string, WorkGraphHierarchyInputNode>();
1133
+ for (const node of nodes) byId.set(node.id, node);
1134
+
1135
+ const violations: WorkGraphHierarchyViolation[] = [];
1136
+ for (const node of nodes) {
1137
+ const parentId = node.parentId ?? null;
1138
+ const parent = parentId === null ? undefined : byId.get(parentId);
1139
+ const allowedParents = PARENT_TYPE_MATRIX[node.type];
1140
+ const valid =
1141
+ parentId === null
1142
+ ? allowedParents.length === 0
1143
+ : parent !== undefined && allowedParents.includes(parent.type);
1144
+
1145
+ if (!valid) {
1146
+ const violation = makeViolation(node, parent);
1147
+ if (options.throwOnViolation) throw new WorkGraphHierarchyInvariantError(violation);
1148
+ violations.push(violation);
1149
+ }
1150
+ }
1151
+
1152
+ return { valid: violations.length === 0, violations };
1153
+ }