@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.
- package/dist/__tests__/completion-operation-contracts.test.d.ts +8 -0
- package/dist/__tests__/completion-operation-contracts.test.d.ts.map +1 -0
- package/dist/__tests__/completion-operation-contracts.test.js +114 -0
- package/dist/__tests__/completion-operation-contracts.test.js.map +1 -0
- package/dist/__tests__/operation-envelope-validation.test.d.ts +8 -0
- package/dist/__tests__/operation-envelope-validation.test.d.ts.map +1 -0
- package/dist/__tests__/operation-envelope-validation.test.js +105 -0
- package/dist/__tests__/operation-envelope-validation.test.js.map +1 -0
- package/dist/__tests__/satisfies-atom.test.d.ts +24 -0
- package/dist/__tests__/satisfies-atom.test.d.ts.map +1 -0
- package/dist/__tests__/satisfies-atom.test.js +320 -0
- package/dist/__tests__/satisfies-atom.test.js.map +1 -0
- package/dist/__tests__/validator-types.test.d.ts +13 -0
- package/dist/__tests__/validator-types.test.d.ts.map +1 -0
- package/dist/__tests__/validator-types.test.js +293 -0
- package/dist/__tests__/validator-types.test.js.map +1 -0
- package/dist/__tests__/workgraph-context-scaffold-contracts.test.d.ts +8 -0
- package/dist/__tests__/workgraph-context-scaffold-contracts.test.d.ts.map +1 -0
- package/dist/__tests__/workgraph-context-scaffold-contracts.test.js +188 -0
- package/dist/__tests__/workgraph-context-scaffold-contracts.test.js.map +1 -0
- package/dist/__tests__/workgraph-operation-contracts.test.d.ts +8 -0
- package/dist/__tests__/workgraph-operation-contracts.test.d.ts.map +1 -0
- package/dist/__tests__/workgraph-operation-contracts.test.js +115 -0
- package/dist/__tests__/workgraph-operation-contracts.test.js.map +1 -0
- package/dist/config.d.ts +56 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/data-accessor.d.ts +194 -0
- package/dist/data-accessor.d.ts.map +1 -1
- package/dist/data-accessor.js.map +1 -1
- package/dist/dispatch/__tests__/docs-update-registry.test.d.ts +2 -0
- package/dist/dispatch/__tests__/docs-update-registry.test.d.ts.map +1 -0
- package/dist/dispatch/__tests__/docs-update-registry.test.js +27 -0
- package/dist/dispatch/__tests__/docs-update-registry.test.js.map +1 -0
- package/dist/dispatch/__tests__/operation-envelope-snapshots.test.d.ts +2 -0
- package/dist/dispatch/__tests__/operation-envelope-snapshots.test.d.ts.map +1 -0
- package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js +265 -0
- package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js.map +1 -0
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +487 -7
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/evidence-atom-schema.d.ts +91 -0
- package/dist/evidence-atom-schema.d.ts.map +1 -1
- package/dist/evidence-atom-schema.js +162 -1
- package/dist/evidence-atom-schema.js.map +1 -1
- package/dist/exit-codes.d.ts +41 -1
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +40 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/index.d.ts +17 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/operation-envelope-validation.d.ts +79 -0
- package/dist/operation-envelope-validation.d.ts.map +1 -0
- package/dist/operation-envelope-validation.js +219 -0
- package/dist/operation-envelope-validation.js.map +1 -0
- package/dist/operations/__tests__/agent-operation-contract.test.d.ts +2 -0
- package/dist/operations/__tests__/agent-operation-contract.test.d.ts.map +1 -0
- package/dist/operations/__tests__/agent-operation-contract.test.js +128 -0
- package/dist/operations/__tests__/agent-operation-contract.test.js.map +1 -0
- package/dist/operations/__tests__/tasks-input-contracts.test.js +15 -0
- package/dist/operations/__tests__/tasks-input-contracts.test.js.map +1 -1
- package/dist/operations/agent-operation-contract.d.ts +227 -0
- package/dist/operations/agent-operation-contract.d.ts.map +1 -0
- package/dist/operations/agent-operation-contract.js +38 -0
- package/dist/operations/agent-operation-contract.js.map +1 -0
- package/dist/operations/docs.d.ts +19 -1
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/docs.js +20 -0
- package/dist/operations/docs.js.map +1 -1
- package/dist/operations/focus.d.ts +1 -1
- package/dist/operations/index.d.ts +1 -0
- package/dist/operations/index.d.ts.map +1 -1
- package/dist/operations/index.js +1 -0
- package/dist/operations/index.js.map +1 -1
- package/dist/operations/orchestrate.d.ts +66 -0
- package/dist/operations/orchestrate.d.ts.map +1 -1
- package/dist/operations/tasks.d.ts +617 -11
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js +22 -3
- package/dist/operations/tasks.js.map +1 -1
- package/dist/task-record.d.ts +15 -0
- package/dist/task-record.d.ts.map +1 -1
- package/dist/task.d.ts +52 -5
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js.map +1 -1
- package/dist/tasks.d.ts +944 -0
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +198 -1
- package/dist/tasks.js.map +1 -1
- package/dist/validator/index.d.ts +375 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +162 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/workgraph.d.ts +1950 -0
- package/dist/workgraph.d.ts.map +1 -0
- package/dist/workgraph.js +449 -0
- package/dist/workgraph.js.map +1 -0
- package/package.json +10 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/completion-operation-contracts.test.ts +154 -0
- package/src/__tests__/operation-envelope-validation.test.ts +140 -0
- package/src/__tests__/satisfies-atom.test.ts +383 -0
- package/src/__tests__/validator-types.test.ts +359 -0
- package/src/__tests__/workgraph-context-scaffold-contracts.test.ts +232 -0
- package/src/__tests__/workgraph-operation-contracts.test.ts +151 -0
- package/src/config.ts +58 -0
- package/src/data-accessor.ts +213 -0
- package/src/dispatch/__tests__/docs-update-registry.test.ts +35 -0
- package/src/dispatch/__tests__/operation-envelope-snapshots.test.ts +283 -0
- package/src/dispatch/operations-registry.ts +502 -7
- package/src/evidence-atom-schema.ts +188 -1
- package/src/exit-codes.ts +42 -0
- package/src/index.ts +230 -2
- package/src/operation-envelope-validation.ts +319 -0
- package/src/operations/__tests__/agent-operation-contract.test.ts +161 -0
- package/src/operations/__tests__/tasks-input-contracts.test.ts +30 -0
- package/src/operations/agent-operation-contract.ts +253 -0
- package/src/operations/docs.ts +27 -7
- package/src/operations/focus.ts +1 -1
- package/src/operations/index.ts +1 -0
- package/src/operations/orchestrate.ts +71 -0
- package/src/operations/tasks.ts +662 -15
- package/src/task-record.ts +16 -0
- package/src/task.ts +54 -5
- package/src/tasks.ts +558 -0
- package/src/validator/index.ts +385 -0
- 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
|
/**
|
package/src/data-accessor.ts
CHANGED
|
@@ -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
|
+
});
|