@cleocode/contracts 2026.5.121 → 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 (128) 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.d.ts +24 -0
  10. package/dist/__tests__/satisfies-atom.test.d.ts.map +1 -0
  11. package/dist/__tests__/satisfies-atom.test.js +320 -0
  12. package/dist/__tests__/satisfies-atom.test.js.map +1 -0
  13. package/dist/__tests__/validator-types.test.d.ts +13 -0
  14. package/dist/__tests__/validator-types.test.d.ts.map +1 -0
  15. package/dist/__tests__/validator-types.test.js +293 -0
  16. package/dist/__tests__/validator-types.test.js.map +1 -0
  17. package/dist/__tests__/workgraph-context-scaffold-contracts.test.d.ts +8 -0
  18. package/dist/__tests__/workgraph-context-scaffold-contracts.test.d.ts.map +1 -0
  19. package/dist/__tests__/workgraph-context-scaffold-contracts.test.js +188 -0
  20. package/dist/__tests__/workgraph-context-scaffold-contracts.test.js.map +1 -0
  21. package/dist/__tests__/workgraph-operation-contracts.test.d.ts +8 -0
  22. package/dist/__tests__/workgraph-operation-contracts.test.d.ts.map +1 -0
  23. package/dist/__tests__/workgraph-operation-contracts.test.js +115 -0
  24. package/dist/__tests__/workgraph-operation-contracts.test.js.map +1 -0
  25. package/dist/config.d.ts +56 -0
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/data-accessor.d.ts +194 -0
  28. package/dist/data-accessor.d.ts.map +1 -1
  29. package/dist/data-accessor.js.map +1 -1
  30. package/dist/dispatch/__tests__/docs-update-registry.test.d.ts +2 -0
  31. package/dist/dispatch/__tests__/docs-update-registry.test.d.ts.map +1 -0
  32. package/dist/dispatch/__tests__/docs-update-registry.test.js +27 -0
  33. package/dist/dispatch/__tests__/docs-update-registry.test.js.map +1 -0
  34. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.d.ts +2 -0
  35. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.d.ts.map +1 -0
  36. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js +265 -0
  37. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js.map +1 -0
  38. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  39. package/dist/dispatch/operations-registry.js +487 -7
  40. package/dist/dispatch/operations-registry.js.map +1 -1
  41. package/dist/evidence-atom-schema.d.ts +91 -0
  42. package/dist/evidence-atom-schema.d.ts.map +1 -1
  43. package/dist/evidence-atom-schema.js +162 -1
  44. package/dist/evidence-atom-schema.js.map +1 -1
  45. package/dist/exit-codes.d.ts +41 -1
  46. package/dist/exit-codes.d.ts.map +1 -1
  47. package/dist/exit-codes.js +40 -0
  48. package/dist/exit-codes.js.map +1 -1
  49. package/dist/index.d.ts +17 -9
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +12 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/operation-envelope-validation.d.ts +79 -0
  54. package/dist/operation-envelope-validation.d.ts.map +1 -0
  55. package/dist/operation-envelope-validation.js +219 -0
  56. package/dist/operation-envelope-validation.js.map +1 -0
  57. package/dist/operations/__tests__/agent-operation-contract.test.d.ts +2 -0
  58. package/dist/operations/__tests__/agent-operation-contract.test.d.ts.map +1 -0
  59. package/dist/operations/__tests__/agent-operation-contract.test.js +128 -0
  60. package/dist/operations/__tests__/agent-operation-contract.test.js.map +1 -0
  61. package/dist/operations/__tests__/tasks-input-contracts.test.js +15 -0
  62. package/dist/operations/__tests__/tasks-input-contracts.test.js.map +1 -1
  63. package/dist/operations/agent-operation-contract.d.ts +227 -0
  64. package/dist/operations/agent-operation-contract.d.ts.map +1 -0
  65. package/dist/operations/agent-operation-contract.js +38 -0
  66. package/dist/operations/agent-operation-contract.js.map +1 -0
  67. package/dist/operations/docs.d.ts +19 -1
  68. package/dist/operations/docs.d.ts.map +1 -1
  69. package/dist/operations/docs.js +20 -0
  70. package/dist/operations/docs.js.map +1 -1
  71. package/dist/operations/focus.d.ts +1 -1
  72. package/dist/operations/index.d.ts +1 -0
  73. package/dist/operations/index.d.ts.map +1 -1
  74. package/dist/operations/index.js +1 -0
  75. package/dist/operations/index.js.map +1 -1
  76. package/dist/operations/orchestrate.d.ts +66 -0
  77. package/dist/operations/orchestrate.d.ts.map +1 -1
  78. package/dist/operations/tasks.d.ts +617 -11
  79. package/dist/operations/tasks.d.ts.map +1 -1
  80. package/dist/operations/tasks.js +22 -3
  81. package/dist/operations/tasks.js.map +1 -1
  82. package/dist/task-record.d.ts +15 -0
  83. package/dist/task-record.d.ts.map +1 -1
  84. package/dist/task.d.ts +52 -5
  85. package/dist/task.d.ts.map +1 -1
  86. package/dist/task.js.map +1 -1
  87. package/dist/tasks.d.ts +944 -0
  88. package/dist/tasks.d.ts.map +1 -1
  89. package/dist/tasks.js +198 -1
  90. package/dist/tasks.js.map +1 -1
  91. package/dist/validator/index.d.ts +375 -0
  92. package/dist/validator/index.d.ts.map +1 -0
  93. package/dist/validator/index.js +162 -0
  94. package/dist/validator/index.js.map +1 -0
  95. package/dist/workgraph.d.ts +1950 -0
  96. package/dist/workgraph.d.ts.map +1 -0
  97. package/dist/workgraph.js +449 -0
  98. package/dist/workgraph.js.map +1 -0
  99. package/package.json +10 -2
  100. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  101. package/src/__tests__/completion-operation-contracts.test.ts +154 -0
  102. package/src/__tests__/operation-envelope-validation.test.ts +140 -0
  103. package/src/__tests__/satisfies-atom.test.ts +383 -0
  104. package/src/__tests__/validator-types.test.ts +359 -0
  105. package/src/__tests__/workgraph-context-scaffold-contracts.test.ts +232 -0
  106. package/src/__tests__/workgraph-operation-contracts.test.ts +151 -0
  107. package/src/config.ts +58 -0
  108. package/src/data-accessor.ts +213 -0
  109. package/src/dispatch/__tests__/docs-update-registry.test.ts +35 -0
  110. package/src/dispatch/__tests__/operation-envelope-snapshots.test.ts +283 -0
  111. package/src/dispatch/operations-registry.ts +502 -7
  112. package/src/evidence-atom-schema.ts +188 -1
  113. package/src/exit-codes.ts +42 -0
  114. package/src/index.ts +230 -2
  115. package/src/operation-envelope-validation.ts +319 -0
  116. package/src/operations/__tests__/agent-operation-contract.test.ts +161 -0
  117. package/src/operations/__tests__/tasks-input-contracts.test.ts +30 -0
  118. package/src/operations/agent-operation-contract.ts +253 -0
  119. package/src/operations/docs.ts +27 -7
  120. package/src/operations/focus.ts +1 -1
  121. package/src/operations/index.ts +1 -0
  122. package/src/operations/orchestrate.ts +71 -0
  123. package/src/operations/tasks.ts +662 -15
  124. package/src/task-record.ts +16 -0
  125. package/src/task.ts +54 -5
  126. package/src/tasks.ts +558 -0
  127. package/src/validator/index.ts +385 -0
  128. package/src/workgraph.ts +1153 -0
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Contract tests for the PM-Core V2 WorkGraph dispatch surface.
3
+ *
4
+ * @task T10606
5
+ * @saga T10538
6
+ */
7
+
8
+ import { describe, expect, it } from 'vitest';
9
+ import { OPERATIONS } from '../dispatch/operations-registry.js';
10
+ import {
11
+ tasksFrontierParamsSchema,
12
+ tasksFrontierResultSchema,
13
+ tasksRollupParamsSchema,
14
+ tasksRollupResultSchema,
15
+ tasksTraverseParamsSchema,
16
+ tasksTraverseResultSchema,
17
+ tasksTreeParamsSchema,
18
+ tasksTreeResultSchema,
19
+ tasksWorkGraphAuditParamsSchema,
20
+ tasksWorkGraphAuditResultSchema,
21
+ } from '../workgraph.js';
22
+
23
+ const requiredOperations = ['traverse', 'tree', 'rollup', 'frontier', 'workgraph.audit'] as const;
24
+
25
+ function operation(name: (typeof requiredOperations)[number]) {
26
+ const def = OPERATIONS.find(
27
+ (candidate) => candidate.domain === 'tasks' && candidate.operation === name,
28
+ );
29
+ if (def === undefined) throw new Error(`missing tasks.${name}`);
30
+ return def;
31
+ }
32
+
33
+ describe('WorkGraph operation contract schemas', () => {
34
+ it('validates params and results for every WorkGraph read operation', () => {
35
+ expect(
36
+ tasksTraverseParamsSchema.parse({
37
+ rootId: 'T1',
38
+ direction: 'descendants',
39
+ cursor: '0001:T2',
40
+ limit: 25,
41
+ }),
42
+ ).toEqual({
43
+ rootId: 'T1',
44
+ direction: 'descendants',
45
+ cursor: '0001:T2',
46
+ limit: 25,
47
+ });
48
+ expect(
49
+ tasksTraverseResultSchema.parse({
50
+ rootId: 'T1',
51
+ direction: 'descendants',
52
+ nodes: [],
53
+ edges: [],
54
+ pageInfo: { hasMore: false },
55
+ }).pageInfo.hasMore,
56
+ ).toBe(false);
57
+
58
+ expect(tasksTreeParamsSchema.parse({ rootId: 'T1', maxDepth: 2, limit: 10 })).toEqual({
59
+ rootId: 'T1',
60
+ maxDepth: 2,
61
+ limit: 10,
62
+ });
63
+ expect(
64
+ tasksTreeResultSchema.parse({
65
+ rootId: 'T1',
66
+ nodes: [],
67
+ edges: [],
68
+ pageInfo: { hasMore: false },
69
+ }).nodes,
70
+ ).toEqual([]);
71
+
72
+ expect(tasksRollupParamsSchema.parse({ rootId: 'T1' })).toEqual({ rootId: 'T1' });
73
+ expect(
74
+ tasksRollupResultSchema.parse({
75
+ rootId: 'T1',
76
+ direct: { total: 0, byStatus: {}, byType: {} },
77
+ subtree: { total: 0, byStatus: {}, byType: {} },
78
+ percentDenominator: { basis: 'subtree-total', total: 0, description: 'subtree total' },
79
+ percentages: { done: 0, active: 0, blocked: 0, pending: 0, cancelled: 0 },
80
+ staleProjection: false,
81
+ projectionMismatches: [],
82
+ }).staleProjection,
83
+ ).toBe(false);
84
+
85
+ expect(tasksFrontierParamsSchema.parse({ rootId: 'T1', role: 'worker' })).toEqual({
86
+ rootId: 'T1',
87
+ role: 'worker',
88
+ });
89
+ expect(
90
+ tasksFrontierResultSchema.parse({
91
+ rootId: 'T1',
92
+ role: 'worker',
93
+ groups: { ready: [], blocked: [], blockedBy: [] },
94
+ }).groups.ready,
95
+ ).toEqual([]);
96
+
97
+ expect(
98
+ tasksWorkGraphAuditParamsSchema.parse({ rootId: 'T1', includeRelations: true, limit: 5 }),
99
+ ).toEqual({
100
+ rootId: 'T1',
101
+ includeRelations: true,
102
+ limit: 5,
103
+ });
104
+ expect(
105
+ tasksWorkGraphAuditResultSchema.parse({
106
+ rootId: 'T1',
107
+ hierarchy: { valid: true, violations: [] },
108
+ traversal: {
109
+ rootId: 'T1',
110
+ direction: 'descendants',
111
+ nodes: [],
112
+ edges: [],
113
+ pageInfo: { hasMore: false },
114
+ },
115
+ frontier: { rootId: 'T1', groups: { ready: [], blocked: [], blockedBy: [] } },
116
+ rollup: {
117
+ rootId: 'T1',
118
+ direct: { total: 0, byStatus: {}, byType: {} },
119
+ subtree: { total: 0, byStatus: {}, byType: {} },
120
+ percentDenominator: { basis: 'subtree-total', total: 0, description: 'subtree total' },
121
+ percentages: { done: 0, active: 0, blocked: 0, pending: 0, cancelled: 0 },
122
+ staleProjection: false,
123
+ projectionMismatches: [],
124
+ },
125
+ relationEdges: { rootId: 'T1', direction: 'both', edges: [] },
126
+ }).hierarchy.valid,
127
+ ).toBe(true);
128
+ });
129
+
130
+ it('publishes operation metadata with explicit pagination params', () => {
131
+ for (const name of requiredOperations) {
132
+ const def = operation(name);
133
+ expect(def.gateway).toBe('query');
134
+ expect(def.domain).toBe('tasks');
135
+ expect(def.idempotent).toBe(true);
136
+ expect(def.params ?? []).not.toEqual([]);
137
+ }
138
+
139
+ expect(operation('traverse').requiredParams).toEqual(['rootId', 'direction']);
140
+ expect(operation('tree').requiredParams).toEqual(['rootId']);
141
+ expect(operation('rollup').requiredParams).toEqual(['rootId']);
142
+ expect(operation('frontier').requiredParams).toEqual(['rootId']);
143
+ expect(operation('workgraph.audit').requiredParams).toEqual(['rootId']);
144
+
145
+ for (const name of ['traverse', 'tree', 'workgraph.audit'] as const) {
146
+ const params = operation(name).params?.map((param) => param.name) ?? [];
147
+ expect(params).toContain('cursor');
148
+ expect(params).toContain('limit');
149
+ }
150
+ });
151
+ });
package/src/config.ts CHANGED
@@ -530,6 +530,50 @@ export interface DecisionsConfig {
530
530
  validatorConfidenceThreshold?: number;
531
531
  }
532
532
 
533
+ /**
534
+ * Operating mode for the Lead-tier wave roll-up
535
+ * (`packages/core/src/orchestration/lead-rollup.ts`).
536
+ *
537
+ * - `'passive'` — Default, backward-compatible behaviour. `rollupWaveStatus` /
538
+ * `rollupEpicStatus` compute the rollup contract from manifest +
539
+ * verification rows only. The Lead reads the result and decides what to do
540
+ * externally.
541
+ * - `'active'` — Enables the Lead↔Worker Max-N loop scaffolded by T10383
542
+ * (E-VALIDATOR-ROLE). The rollup will additionally surface retry-eligible
543
+ * workers and emit Lead-initiated retry signals. The wiring of retries
544
+ * themselves lives in T10512 — this flag is the gate.
545
+ * - `'auto'` — Reserved for future heuristic selection (e.g. switch to
546
+ * active mode iff the epic has ≥N pending workers). Treated as `'passive'`
547
+ * at runtime until the heuristic ships.
548
+ *
549
+ * @task T10513
550
+ * @saga T10377
551
+ * @adr ADR-070
552
+ */
553
+ export type LeadRollupMode = 'passive' | 'active' | 'auto';
554
+
555
+ /**
556
+ * Lead-tier wave roll-up configuration.
557
+ *
558
+ * Single-key config block gating the Lead↔Worker Max-N loop introduced by
559
+ * SG-IVTR-AC-BINDING (T10377) and council action #9 of E-VALIDATOR-ROLE
560
+ * (T10383). The whole point of this block is to keep the existing
561
+ * `rollupWaveStatus` / `rollupEpicStatus` function signatures unchanged —
562
+ * callers pick up new behaviour by flipping the config key, never by
563
+ * threading a new parameter.
564
+ *
565
+ * @task T10513
566
+ * @saga T10377
567
+ */
568
+ export interface LeadRollupConfig {
569
+ /**
570
+ * Roll-up operating mode. Defaults to `'passive'` for backward compatibility.
571
+ *
572
+ * @defaultValue 'passive'
573
+ */
574
+ mode?: LeadRollupMode;
575
+ }
576
+
533
577
  /** CLEO project configuration (config.json). */
534
578
  export interface CleoConfig {
535
579
  /** Configuration schema version string. */
@@ -603,6 +647,20 @@ export interface CleoConfig {
603
647
  * @task T9410
604
648
  */
605
649
  auth?: AuthConfig;
650
+ /**
651
+ * Lead-tier wave roll-up configuration.
652
+ *
653
+ * Gates the Lead↔Worker Max-N loop scaffolded under SG-IVTR-AC-BINDING
654
+ * (T10377) and council action #9 of E-VALIDATOR-ROLE (T10383). When
655
+ * absent, the roll-up defaults to `{ mode: 'passive' }` — the legacy
656
+ * compute-from-manifest behaviour — so existing callers continue to work
657
+ * unchanged.
658
+ *
659
+ * @defaultValue undefined
660
+ * @task T10513
661
+ * @saga T10377
662
+ */
663
+ leadRollup?: LeadRollupConfig;
606
664
  }
607
665
 
608
666
  /**
@@ -107,6 +107,127 @@ export interface TaskFieldUpdates {
107
107
  pipelineStage?: string | null;
108
108
  }
109
109
 
110
+ /**
111
+ * A row of the `task_acceptance_criteria` table (T10502).
112
+ *
113
+ * @task T10508
114
+ */
115
+ export interface AcRow {
116
+ /** UUIDv4 stable identifier, immutable for the AC's lifetime. */
117
+ id: string;
118
+ /** Owning task ID. */
119
+ taskId: string;
120
+ /** 1-based ordinal — never reused per task (gaps remain on shrink). */
121
+ ordinal: number;
122
+ /** Typed completion criterion discriminator per ADR-088. */
123
+ kind: 'text' | 'child_task' | 'evidence_bound';
124
+ /** Stable per-task source key for idempotent criteria projection/upsert. */
125
+ sourceKey: string;
126
+ /** Optional child task target; only `kind='child_task'` may populate it. */
127
+ targetTaskId: string | null;
128
+ /** Compatibility projection owner (for example: legacy, direct, parent-child). */
129
+ projection: string;
130
+ /** The AC statement text. Structured gates are serialised as JSON. */
131
+ text: string;
132
+ /** ISO-8601 timestamp the row was created. */
133
+ createdAt: string;
134
+ /** ISO-8601 last-edit timestamp; null until first edit. */
135
+ updatedAt: string | null;
136
+ /** Optional sha256(text) snapshot; writers MAY populate, readers MUST treat null as "unknown". */
137
+ contentHash: string | null;
138
+ }
139
+
140
+ /** Machine-readable AC child-projection drift codes for doctor/audit output. */
141
+ export type AcProjectionAuditFindingCode =
142
+ | 'missing_child_task_row'
143
+ | 'extra_child_task_row'
144
+ | 'mismatched_child_task_row'
145
+ | 'stale_child_task_projection';
146
+
147
+ /** Dirty/clean status for an AC projection audit scan. */
148
+ export type AcProjectionAuditStatus = 'clean' | 'dirty';
149
+
150
+ /** Field-level child projection mismatch surfaced by doctor/audit callers. */
151
+ export interface AcProjectionAuditFinding {
152
+ /** Stable machine-readable finding code. */
153
+ code: AcProjectionAuditFindingCode;
154
+ /** Parent task whose AC rows were audited. */
155
+ parentId: string;
156
+ /** Direct child expected by WorkGraph containment, when applicable. */
157
+ childId?: string;
158
+ /** Existing AC row id involved in the finding, when applicable. */
159
+ acId?: string;
160
+ /** Compared row field, or `row` for whole-row missing/extra findings. */
161
+ field: 'row' | 'kind' | 'sourceKey' | 'targetTaskId' | 'projection' | 'text' | 'contentHash';
162
+ /** Expected canonical value. */
163
+ expected: string | null;
164
+ /** Actual observed value. */
165
+ actual: string | null;
166
+ /** True when this finding proves cached projection state is dirty/stale. */
167
+ dirty: true;
168
+ }
169
+
170
+ /** Typed result returned by AC projection doctor/audit scanners. */
171
+ export interface AcProjectionAuditResult {
172
+ /** Parent task whose child_task projection rows were audited. */
173
+ parentId: string;
174
+ /** Clean when no findings were emitted, dirty otherwise. */
175
+ status: AcProjectionAuditStatus;
176
+ /** Boolean convenience flag for CLIs that render dirty state. */
177
+ dirty: boolean;
178
+ /** Number of direct children WorkGraph says should be projected. */
179
+ expectedRows: number;
180
+ /** Number of existing child_task projection rows observed on the parent. */
181
+ actualRows: number;
182
+ /** Stable sha256 over the expected child projection state. */
183
+ freshnessFingerprint: string;
184
+ /** True when at least one finding indicates stale/missing/extra projection state. */
185
+ staleProjection: boolean;
186
+ /** Typed findings suitable for JSON doctor/audit output. */
187
+ findings: readonly AcProjectionAuditFinding[];
188
+ }
189
+
190
+ /**
191
+ * A row of the `evidence_ac_bindings` table (T10503) — the M:N join between
192
+ * evidence atoms and acceptance criteria. Powers the AC-coverage gate
193
+ * (T10509) — "what evidence has been recorded against this AC?".
194
+ *
195
+ * @task T10509
196
+ * @saga T10377 (SG-IVTR-AC-BINDING)
197
+ */
198
+ export interface AcBindingRow {
199
+ /** UUIDv4 — set by the writer (T10505/T10506). */
200
+ id: string;
201
+ /** Stable hash / composite key of the evidence atom. NOT an FK. */
202
+ evidenceAtomId: string;
203
+ /** FK → `task_acceptance_criteria(id)`. */
204
+ acId: string;
205
+ /** One of {direct, satisfies, coverage}. */
206
+ bindingType: 'direct' | 'satisfies' | 'coverage';
207
+ /** ISO-8601 timestamp of binding creation. */
208
+ createdAt: string;
209
+ }
210
+
211
+ /** Query options for bounded reads from the append-only task audit log. @task T10594 */
212
+ export interface TaskAuditLogQuery {
213
+ taskIds?: readonly string[];
214
+ actions?: readonly string[];
215
+ since?: string;
216
+ limit?: number;
217
+ }
218
+
219
+ /** DataAccessor-facing shape of audit_log rows used by completion context packs. @task T10594 */
220
+ export interface TaskAuditLogRow {
221
+ id: string;
222
+ timestamp: string;
223
+ action: string;
224
+ taskId: string;
225
+ actor: string;
226
+ detailsJson: string | null;
227
+ beforeJson: string | null;
228
+ afterJson: string | null;
229
+ }
230
+
110
231
  /**
111
232
  * Subset of DataAccessor methods available inside a transaction callback.
112
233
  * Write-only — reads use the outer accessor (snapshot isolation).
@@ -117,6 +238,8 @@ export interface TransactionAccessor {
117
238
  removeSingleTask(taskId: string): Promise<void>;
118
239
  setMetaValue(key: string, value: unknown): Promise<void>;
119
240
  updateTaskFields(taskId: string, fields: TaskFieldUpdates): Promise<void>;
241
+ /** Get direct non-archived children inside the caller-owned transaction. @task T10590 */
242
+ getChildren(parentId: string): Promise<Task[]>;
120
243
  appendLog(entry: Record<string, unknown>): Promise<void>;
121
244
  /** Persist a relation row to task_relations. @task T9514 */
122
245
  addRelation(
@@ -129,8 +252,80 @@ export interface TransactionAccessor {
129
252
  removeRelation(taskId: string, relatedTo: string, relationType?: string): Promise<void>;
130
253
  /** Remove all relations for a task (both directions) — used for set-replace. @task T9514 */
131
254
  clearRelations(taskId: string): Promise<void>;
255
+ /**
256
+ * Insert AC rows into `task_acceptance_criteria` with caller-supplied
257
+ * UUIDs and ordinals. Ordinals MUST NOT collide with existing rows for
258
+ * the same task — the UNIQUE (task_id, ordinal) index enforces this.
259
+ * @task T10508
260
+ */
261
+ insertAcRows(
262
+ rows: Array<{
263
+ id: string;
264
+ taskId: string;
265
+ ordinal: number;
266
+ text: string;
267
+ kind?: 'text' | 'child_task' | 'evidence_bound';
268
+ sourceKey?: string;
269
+ targetTaskId?: string | null;
270
+ projection?: string;
271
+ contentHash?: string | null;
272
+ }>,
273
+ ): Promise<void>;
274
+ /**
275
+ * Read all AC rows for a task, ordered by ordinal ASC.
276
+ * Available inside transactions for shrink/replace flows that need to
277
+ * read the current state before deletion.
278
+ * @task T10508
279
+ */
280
+ getAcRows(taskId: string): Promise<AcRow[]>;
281
+ /**
282
+ * Delete all AC rows for a task. Used by update-replace-all + update-shrink
283
+ * flows AFTER the history rows have been appended.
284
+ * @task T10508
285
+ */
286
+ deleteAcRowsForTask(taskId: string): Promise<void>;
287
+ /**
288
+ * Append a history row to `task_acceptance_criteria_history` capturing the
289
+ * AC text that is about to be superseded.
290
+ * @task T10508
291
+ */
292
+ appendAcHistory(
293
+ rows: Array<{ acId: string; previousText: string; reason: string }>,
294
+ ): Promise<void>;
295
+ /**
296
+ * Read all `evidence_ac_bindings` rows whose `ac_id` ∈ the given set.
297
+ * Used by the AC-coverage gate (T10509) to compute which ACs are
298
+ * satisfied vs unsatisfied inside the same transaction that flips the
299
+ * task to `done`.
300
+ *
301
+ * Returns the empty array when `acIds` is empty.
302
+ *
303
+ * @task T10509
304
+ */
305
+ getAcBindings(acIds: readonly string[]): Promise<AcBindingRow[]>;
306
+ /**
307
+ * Insert rows into `evidence_ac_bindings`. Used by the Validator SDK
308
+ * tools (T10511) to persist coverage bindings transactionally after a
309
+ * Validator attestation. The UNIQUE (evidence_atom_id, ac_id, binding_type)
310
+ * index collapses idempotent re-inserts via `ON CONFLICT DO NOTHING`.
311
+ *
312
+ * No-op when `rows` is empty.
313
+ *
314
+ * @task T10511
315
+ * @saga T10377 (SG-IVTR-AC-BINDING)
316
+ */
317
+ insertAcBindings(
318
+ rows: Array<{
319
+ id: string;
320
+ evidenceAtomId: string;
321
+ acId: string;
322
+ bindingType: 'direct' | 'satisfies' | 'coverage';
323
+ }>,
324
+ ): Promise<void>;
132
325
  }
133
326
 
327
+ // Re-export AcRow at the module level for both transaction + outer accessor use.
328
+
134
329
  /**
135
330
  * DataAccessor interface.
136
331
  *
@@ -163,6 +358,9 @@ export interface DataAccessor {
163
358
  /** Append an entry to the audit log. */
164
359
  appendLog(entry: Record<string, unknown>): Promise<void>;
165
360
 
361
+ /** Query recent task audit rows by task id/action, newest first. @task T10594 */
362
+ queryAuditLog(query: TaskAuditLogQuery): Promise<TaskAuditLogRow[]>;
363
+
166
364
  // ---- Lifecycle ----
167
365
 
168
366
  /** Release any resources (close DB connections, etc.). */
@@ -193,6 +391,21 @@ export interface DataAccessor {
193
391
  /** Remove a row from the task_relations table (T9240). */
194
392
  removeRelation(taskId: string, relatedTo: string, relationType?: string): Promise<void>;
195
393
 
394
+ /**
395
+ * Read AC rows for a task from `task_acceptance_criteria`, ordered by
396
+ * ordinal ASC. Returns the empty array if no rows exist.
397
+ * @task T10508
398
+ */
399
+ getAcRows(taskId: string): Promise<AcRow[]>;
400
+
401
+ /**
402
+ * Read `evidence_ac_bindings` rows whose `ac_id` ∈ the given set.
403
+ * Powers the AC-coverage gate (T10509). Returns the empty array when
404
+ * `acIds` is empty or no bindings exist for the supplied ids.
405
+ * @task T10509
406
+ */
407
+ getAcBindings(acIds: readonly string[]): Promise<AcBindingRow[]>;
408
+
196
409
  // ---- Metadata (schema_meta KV store) ----
197
410
 
198
411
  /** Read a typed value from the metadata store. Returns null if not found. */
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DOCS_LIFECYCLE_STATUSES } from '../../operations/docs.js';
3
+ import { OPERATIONS } from '../operations-registry.js';
4
+
5
+ describe('docs.update registry flags (T10617)', () => {
6
+ it('exposes registry-driven dry-run and strict preflight CLI params', () => {
7
+ const updateOp = OPERATIONS.find(
8
+ (op) => op.domain === 'docs' && op.operation === 'update' && op.gateway === 'mutate',
9
+ );
10
+
11
+ expect(updateOp).toBeDefined();
12
+ const dryRun = updateOp?.params?.find((param) => param.name === 'dryRun');
13
+ const strict = updateOp?.params?.find((param) => param.name === 'strict');
14
+
15
+ expect(dryRun).toMatchObject({
16
+ type: 'boolean',
17
+ required: false,
18
+ cli: { flag: 'dry-run' },
19
+ });
20
+ expect(strict).toMatchObject({
21
+ type: 'boolean',
22
+ required: false,
23
+ });
24
+ });
25
+
26
+ it('derives lifecycle help and enum from the runtime docs.update status SSoT', () => {
27
+ const updateOp = OPERATIONS.find(
28
+ (op) => op.domain === 'docs' && op.operation === 'update' && op.gateway === 'mutate',
29
+ );
30
+ const status = updateOp?.params?.find((param) => param.name === 'status');
31
+
32
+ expect(status?.enum).toEqual(DOCS_LIFECYCLE_STATUSES);
33
+ expect(status?.description).toContain(`Valid: ${DOCS_LIFECYCLE_STATUSES.join('|')}.`);
34
+ });
35
+ });