@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,359 @@
1
+ /**
2
+ * Type-level and runtime shape tests for T10510 Validator role contracts.
3
+ *
4
+ * Covers the AgentRole enum extension and the four envelope types
5
+ * (`ValidatorFinding`, `ValidatorAttestation`, `ValidatorRejection`,
6
+ * `ValidatorVerdict`) plus their Zod schemas and type guards.
7
+ *
8
+ * @task T10510
9
+ * @epic T10383 (E-VALIDATOR-ROLE)
10
+ * @saga T10377 (SG-IVTR-AC-BINDING)
11
+ */
12
+
13
+ import { describe, expect, it } from 'vitest';
14
+ import {
15
+ AGENT_ROLES,
16
+ type AgentRole,
17
+ isAgentRole,
18
+ isValidatorAttestation,
19
+ isValidatorRejection,
20
+ isValidatorVerdict,
21
+ VALIDATOR_ID_REGEX,
22
+ type ValidatorAttestation,
23
+ type ValidatorFinding,
24
+ type ValidatorRejection,
25
+ type ValidatorVerdict,
26
+ validatorAttestationSchema,
27
+ validatorFindingSchema,
28
+ validatorRejectionSchema,
29
+ validatorVerdictSchema,
30
+ } from '../validator/index.js';
31
+
32
+ // ============================================================================
33
+ // Test fixtures
34
+ // ============================================================================
35
+
36
+ const NOW = '2026-05-24T12:00:00Z';
37
+
38
+ const passFinding: ValidatorFinding = {
39
+ acId: '11111111-1111-4111-8111-111111111111',
40
+ status: 'pass',
41
+ reasoning: 'AC satisfied — implementation matches spec',
42
+ checkedAt: NOW,
43
+ };
44
+
45
+ const failFinding: ValidatorFinding = {
46
+ acId: '22222222-2222-4222-8222-222222222222',
47
+ status: 'fail',
48
+ reasoning: 'Missing error handling for the null branch',
49
+ evidenceRefs: ['src/foo.ts:42', 'tests/foo.test.ts:17'],
50
+ checkedAt: NOW,
51
+ };
52
+
53
+ const inconclusiveFinding: ValidatorFinding = {
54
+ acId: '33333333-3333-4333-8333-333333333333',
55
+ status: 'inconclusive',
56
+ reasoning: 'Cannot reproduce the claimed test — file not present',
57
+ checkedAt: NOW,
58
+ };
59
+
60
+ // ============================================================================
61
+ // AgentRole enum
62
+ // ============================================================================
63
+
64
+ describe('AgentRole enum (T10510)', () => {
65
+ it('contains the four canonical roles', () => {
66
+ expect(AGENT_ROLES).toEqual(['orchestrator', 'lead', 'worker', 'validator']);
67
+ });
68
+
69
+ it('AGENT_ROLES is a readonly tuple of length 4', () => {
70
+ // `as const` is a compile-time readonly marker — there is no runtime
71
+ // freeze. The TS type guard below ensures mutation attempts are rejected
72
+ // by the compiler; runtime length is the structural invariant.
73
+ expect(AGENT_ROLES.length).toBe(4);
74
+ // @ts-expect-error — readonly tuple disallows .push at the type level.
75
+ AGENT_ROLES.push;
76
+ });
77
+
78
+ it('AgentRole type compiles for all four values', () => {
79
+ const roles: AgentRole[] = ['orchestrator', 'lead', 'worker', 'validator'];
80
+ expect(roles).toHaveLength(4);
81
+ });
82
+
83
+ it('isAgentRole accepts canonical values', () => {
84
+ for (const r of AGENT_ROLES) {
85
+ expect(isAgentRole(r)).toBe(true);
86
+ }
87
+ });
88
+
89
+ it('isAgentRole rejects unknown strings and non-strings', () => {
90
+ expect(isAgentRole('subagent')).toBe(false);
91
+ expect(isAgentRole('Validator')).toBe(false);
92
+ expect(isAgentRole('')).toBe(false);
93
+ expect(isAgentRole(undefined)).toBe(false);
94
+ expect(isAgentRole(null)).toBe(false);
95
+ expect(isAgentRole(42)).toBe(false);
96
+ expect(isAgentRole({})).toBe(false);
97
+ });
98
+ });
99
+
100
+ // ============================================================================
101
+ // VALIDATOR_ID_REGEX
102
+ // ============================================================================
103
+
104
+ describe('VALIDATOR_ID_REGEX (T10510)', () => {
105
+ it('accepts canonical validator agentIds', () => {
106
+ expect(VALIDATOR_ID_REGEX.test('validator-prime')).toBe(true);
107
+ expect(VALIDATOR_ID_REGEX.test('validator-sec-001')).toBe(true);
108
+ expect(VALIDATOR_ID_REGEX.test('validator-a')).toBe(true);
109
+ expect(VALIDATOR_ID_REGEX.test('validator-9-test')).toBe(true);
110
+ });
111
+
112
+ it('rejects malformed validator agentIds', () => {
113
+ expect(VALIDATOR_ID_REGEX.test('validator-')).toBe(false);
114
+ expect(VALIDATOR_ID_REGEX.test('Validator-prime')).toBe(false);
115
+ expect(VALIDATOR_ID_REGEX.test('validator-Prime')).toBe(false);
116
+ expect(VALIDATOR_ID_REGEX.test('worker-prime')).toBe(false);
117
+ expect(VALIDATOR_ID_REGEX.test('validator-_x')).toBe(false);
118
+ });
119
+ });
120
+
121
+ // ============================================================================
122
+ // ValidatorFinding
123
+ // ============================================================================
124
+
125
+ describe('ValidatorFinding (T10510)', () => {
126
+ it('accepts pass / fail / inconclusive', () => {
127
+ expect(validatorFindingSchema.safeParse(passFinding).success).toBe(true);
128
+ expect(validatorFindingSchema.safeParse(failFinding).success).toBe(true);
129
+ expect(validatorFindingSchema.safeParse(inconclusiveFinding).success).toBe(true);
130
+ });
131
+
132
+ it('rejects empty acId', () => {
133
+ const bad = { ...passFinding, acId: '' };
134
+ expect(validatorFindingSchema.safeParse(bad).success).toBe(false);
135
+ });
136
+
137
+ it('rejects empty reasoning', () => {
138
+ const bad = { ...passFinding, reasoning: '' };
139
+ expect(validatorFindingSchema.safeParse(bad).success).toBe(false);
140
+ });
141
+
142
+ it('rejects unknown status value', () => {
143
+ const bad = { ...passFinding, status: 'warn' };
144
+ expect(validatorFindingSchema.safeParse(bad).success).toBe(false);
145
+ });
146
+
147
+ it('accepts optional evidenceRefs array', () => {
148
+ const withRefs = { ...passFinding, evidenceRefs: ['a', 'b'] };
149
+ expect(validatorFindingSchema.safeParse(withRefs).success).toBe(true);
150
+ });
151
+ });
152
+
153
+ // ============================================================================
154
+ // ValidatorAttestation
155
+ // ============================================================================
156
+
157
+ describe('ValidatorAttestation (T10510)', () => {
158
+ const baseAttestation: ValidatorAttestation = {
159
+ verdict: 'attest',
160
+ taskId: 'T10510',
161
+ validatorId: 'validator-prime',
162
+ findings: [passFinding],
163
+ attestedAt: NOW,
164
+ schemaVersion: '1',
165
+ };
166
+
167
+ it('accepts a well-formed attestation with all passes', () => {
168
+ expect(validatorAttestationSchema.safeParse(baseAttestation).success).toBe(true);
169
+ });
170
+
171
+ it('accepts optional summary', () => {
172
+ const withSummary = { ...baseAttestation, summary: 'All ACs verified' };
173
+ expect(validatorAttestationSchema.safeParse(withSummary).success).toBe(true);
174
+ });
175
+
176
+ it('accepts multiple pass findings', () => {
177
+ const multi = {
178
+ ...baseAttestation,
179
+ findings: [passFinding, { ...passFinding, acId: '44444444-4444-4444-8444-444444444444' }],
180
+ };
181
+ expect(validatorAttestationSchema.safeParse(multi).success).toBe(true);
182
+ });
183
+
184
+ it('REJECTS attestation containing any fail finding', () => {
185
+ const mixed = { ...baseAttestation, findings: [passFinding, failFinding] };
186
+ const r = validatorAttestationSchema.safeParse(mixed);
187
+ expect(r.success).toBe(false);
188
+ });
189
+
190
+ it('REJECTS attestation containing inconclusive finding', () => {
191
+ const mixed = { ...baseAttestation, findings: [passFinding, inconclusiveFinding] };
192
+ expect(validatorAttestationSchema.safeParse(mixed).success).toBe(false);
193
+ });
194
+
195
+ it('REJECTS empty findings array', () => {
196
+ const empty = { ...baseAttestation, findings: [] };
197
+ expect(validatorAttestationSchema.safeParse(empty).success).toBe(false);
198
+ });
199
+
200
+ it('REJECTS malformed validatorId', () => {
201
+ const bad = { ...baseAttestation, validatorId: 'worker-prime' };
202
+ expect(validatorAttestationSchema.safeParse(bad).success).toBe(false);
203
+ });
204
+
205
+ it('REJECTS wrong verdict discriminant', () => {
206
+ const bad = { ...baseAttestation, verdict: 'reject' };
207
+ expect(validatorAttestationSchema.safeParse(bad).success).toBe(false);
208
+ });
209
+
210
+ it('REJECTS wrong schemaVersion', () => {
211
+ const bad = { ...baseAttestation, schemaVersion: '2' };
212
+ expect(validatorAttestationSchema.safeParse(bad).success).toBe(false);
213
+ });
214
+ });
215
+
216
+ // ============================================================================
217
+ // ValidatorRejection
218
+ // ============================================================================
219
+
220
+ describe('ValidatorRejection (T10510)', () => {
221
+ const baseRejection: ValidatorRejection = {
222
+ verdict: 'reject',
223
+ taskId: 'T10510',
224
+ validatorId: 'validator-sec-001',
225
+ findings: [failFinding],
226
+ summary: 'AC#2 fails — null branch unhandled',
227
+ rejectedAt: NOW,
228
+ schemaVersion: '1',
229
+ };
230
+
231
+ it('accepts a well-formed rejection with a fail finding', () => {
232
+ expect(validatorRejectionSchema.safeParse(baseRejection).success).toBe(true);
233
+ });
234
+
235
+ it('accepts a rejection with only an inconclusive finding', () => {
236
+ const inc = { ...baseRejection, findings: [inconclusiveFinding] };
237
+ expect(validatorRejectionSchema.safeParse(inc).success).toBe(true);
238
+ });
239
+
240
+ it('accepts mixed pass + fail findings', () => {
241
+ const mixed = { ...baseRejection, findings: [passFinding, failFinding] };
242
+ expect(validatorRejectionSchema.safeParse(mixed).success).toBe(true);
243
+ });
244
+
245
+ it('REJECTS rejection where every finding is pass', () => {
246
+ const allPass = { ...baseRejection, findings: [passFinding, passFinding] };
247
+ const r = validatorRejectionSchema.safeParse(allPass);
248
+ expect(r.success).toBe(false);
249
+ });
250
+
251
+ it('REJECTS empty findings array', () => {
252
+ const empty = { ...baseRejection, findings: [] };
253
+ expect(validatorRejectionSchema.safeParse(empty).success).toBe(false);
254
+ });
255
+
256
+ it('REJECTS empty summary', () => {
257
+ const bad = { ...baseRejection, summary: '' };
258
+ expect(validatorRejectionSchema.safeParse(bad).success).toBe(false);
259
+ });
260
+
261
+ it('accepts optional remediationHints', () => {
262
+ const withHints = {
263
+ ...baseRejection,
264
+ remediationHints: ['Add null guard to foo()', 'Add regression test'],
265
+ };
266
+ expect(validatorRejectionSchema.safeParse(withHints).success).toBe(true);
267
+ });
268
+ });
269
+
270
+ // ============================================================================
271
+ // ValidatorVerdict discriminated union
272
+ // ============================================================================
273
+
274
+ describe('ValidatorVerdict (T10510)', () => {
275
+ const attestation: ValidatorAttestation = {
276
+ verdict: 'attest',
277
+ taskId: 'T10510',
278
+ validatorId: 'validator-prime',
279
+ findings: [passFinding],
280
+ attestedAt: NOW,
281
+ schemaVersion: '1',
282
+ };
283
+ const rejection: ValidatorRejection = {
284
+ verdict: 'reject',
285
+ taskId: 'T10510',
286
+ validatorId: 'validator-prime',
287
+ findings: [failFinding],
288
+ summary: 'fail',
289
+ rejectedAt: NOW,
290
+ schemaVersion: '1',
291
+ };
292
+
293
+ it('schema accepts both attest and reject envelopes', () => {
294
+ expect(validatorVerdictSchema.safeParse(attestation).success).toBe(true);
295
+ expect(validatorVerdictSchema.safeParse(rejection).success).toBe(true);
296
+ });
297
+
298
+ it('schema rejects envelope with unknown verdict', () => {
299
+ const bad = { ...attestation, verdict: 'maybe' };
300
+ expect(validatorVerdictSchema.safeParse(bad).success).toBe(false);
301
+ });
302
+
303
+ it('discriminant narrows in TypeScript at compile time', () => {
304
+ const handle = (v: ValidatorVerdict): string => {
305
+ if (v.verdict === 'attest') {
306
+ // TS narrows to ValidatorAttestation here
307
+ return `attested ${v.attestedAt}`;
308
+ }
309
+ // TS narrows to ValidatorRejection here
310
+ return `rejected ${v.rejectedAt} — ${v.summary}`;
311
+ };
312
+ expect(handle(attestation)).toContain('attested');
313
+ expect(handle(rejection)).toContain('rejected');
314
+ });
315
+ });
316
+
317
+ // ============================================================================
318
+ // Type guards
319
+ // ============================================================================
320
+
321
+ describe('Validator type guards (T10510)', () => {
322
+ const attestation: ValidatorAttestation = {
323
+ verdict: 'attest',
324
+ taskId: 'T10510',
325
+ validatorId: 'validator-prime',
326
+ findings: [passFinding],
327
+ attestedAt: NOW,
328
+ schemaVersion: '1',
329
+ };
330
+ const rejection: ValidatorRejection = {
331
+ verdict: 'reject',
332
+ taskId: 'T10510',
333
+ validatorId: 'validator-prime',
334
+ findings: [failFinding],
335
+ summary: 'fail',
336
+ rejectedAt: NOW,
337
+ schemaVersion: '1',
338
+ };
339
+
340
+ it('isValidatorAttestation returns true only for attestations', () => {
341
+ expect(isValidatorAttestation(attestation)).toBe(true);
342
+ expect(isValidatorAttestation(rejection)).toBe(false);
343
+ expect(isValidatorAttestation(null)).toBe(false);
344
+ expect(isValidatorAttestation({})).toBe(false);
345
+ });
346
+
347
+ it('isValidatorRejection returns true only for rejections', () => {
348
+ expect(isValidatorRejection(rejection)).toBe(true);
349
+ expect(isValidatorRejection(attestation)).toBe(false);
350
+ expect(isValidatorRejection(null)).toBe(false);
351
+ });
352
+
353
+ it('isValidatorVerdict returns true for both shapes', () => {
354
+ expect(isValidatorVerdict(attestation)).toBe(true);
355
+ expect(isValidatorVerdict(rejection)).toBe(true);
356
+ expect(isValidatorVerdict({ verdict: 'attest' })).toBe(false);
357
+ expect(isValidatorVerdict(null)).toBe(false);
358
+ });
359
+ });
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Contract tests for bounded WorkGraph context packs, readiness, and scaffold envelopes.
3
+ *
4
+ * @task T10609
5
+ * @saga T10538
6
+ */
7
+
8
+ import { describe, expect, it } from 'vitest';
9
+ import {
10
+ workGraphContextPackParamsSchema,
11
+ workGraphContextPackSchema,
12
+ workGraphReadinessParamsSchema,
13
+ workGraphReadinessResultSchema,
14
+ workGraphScaffoldApplyParamsSchema,
15
+ workGraphScaffoldApplyResultSchema,
16
+ workGraphScaffoldValidateParamsSchema,
17
+ workGraphScaffoldValidateResultSchema,
18
+ workGraphSliceParamsSchema,
19
+ workGraphSliceSchema,
20
+ } from '../workgraph.js';
21
+
22
+ const taskNode = {
23
+ id: 'T10609',
24
+ type: 'task',
25
+ title: 'Add contracts',
26
+ status: 'pending',
27
+ priority: 'high',
28
+ parentId: 'T10545',
29
+ } as const;
30
+
31
+ const emptyPageInfo = { hasMore: false } as const;
32
+
33
+ const readyGroups = {
34
+ ready: [
35
+ {
36
+ ...taskNode,
37
+ role: 'worker',
38
+ dependencyBlockers: [],
39
+ gateBlockers: [],
40
+ },
41
+ ],
42
+ blocked: [],
43
+ blockedBy: [],
44
+ } as const;
45
+
46
+ describe('WorkGraph bounded context and scaffold contracts', () => {
47
+ it('validates bounded context packs with graph slices, relation edges, readiness, budget, and omissions', () => {
48
+ expect(
49
+ workGraphContextPackParamsSchema.parse({
50
+ rootId: 'T10545',
51
+ tokenBudget: 1500,
52
+ includeRelations: true,
53
+ includeReadiness: true,
54
+ includeRollup: true,
55
+ limit: 25,
56
+ }),
57
+ ).toEqual({
58
+ rootId: 'T10545',
59
+ tokenBudget: 1500,
60
+ includeRelations: true,
61
+ includeReadiness: true,
62
+ includeRollup: true,
63
+ limit: 25,
64
+ });
65
+
66
+ const parsed = workGraphContextPackSchema.parse({
67
+ rootId: 'T10545',
68
+ generatedAt: '2026-05-26T10:00:00.000Z',
69
+ budget: {
70
+ tokenBudget: 1500,
71
+ estimatedTokens: 1200,
72
+ remainingTokens: 300,
73
+ truncated: true,
74
+ },
75
+ slice: {
76
+ rootId: 'T10545',
77
+ direction: 'descendants',
78
+ nodes: [taskNode],
79
+ edges: [{ fromId: 'T10545', toId: 'T10609', kind: 'contains' }],
80
+ pageInfo: { nextCursor: '0002:T10610', hasMore: true },
81
+ omissions: [
82
+ {
83
+ path: 'slice.nodes[25:]',
84
+ reason: 'budget_exceeded',
85
+ message: 'Additional nodes omitted to stay under token budget',
86
+ estimatedTokens: 400,
87
+ },
88
+ ],
89
+ },
90
+ relationEdges: {
91
+ rootId: 'T10609',
92
+ direction: 'both',
93
+ edges: [
94
+ {
95
+ source: 'dependency',
96
+ fromId: 'T10609',
97
+ toId: 'T10608',
98
+ kind: 'depends_on',
99
+ },
100
+ {
101
+ source: 'relation',
102
+ fromId: 'T10538',
103
+ toId: 'T10545',
104
+ kind: 'groups',
105
+ relationType: 'groups',
106
+ reason: 'Saga membership',
107
+ },
108
+ ],
109
+ },
110
+ readiness: {
111
+ rootId: 'T10545',
112
+ role: 'worker',
113
+ ready: true,
114
+ warnings: [],
115
+ groups: readyGroups,
116
+ },
117
+ rollup: {
118
+ rootId: 'T10545',
119
+ direct: { total: 1, byStatus: { pending: 1 }, byType: { task: 1 } },
120
+ subtree: { total: 1, byStatus: { pending: 1 }, byType: { task: 1 } },
121
+ percentDenominator: { basis: 'subtree-total', total: 1, description: 'subtree total' },
122
+ percentages: { done: 0, active: 0, blocked: 0, pending: 100, cancelled: 0 },
123
+ staleProjection: false,
124
+ projectionMismatches: [],
125
+ },
126
+ omissions: [
127
+ { path: 'rollup.history', reason: 'not_requested', message: 'History not requested' },
128
+ ],
129
+ });
130
+
131
+ expect(parsed.budget.truncated).toBe(true);
132
+ expect(parsed.relationEdges?.edges.map((edge) => edge.source)).toEqual([
133
+ 'dependency',
134
+ 'relation',
135
+ ]);
136
+ expect(parsed.readiness?.groups.ready[0]?.id).toBe('T10609');
137
+ });
138
+
139
+ it('validates graph slice and readiness envelopes independently', () => {
140
+ expect(
141
+ workGraphSliceParamsSchema.parse({
142
+ rootId: 'T10545',
143
+ direction: 'descendants',
144
+ maxDepth: 2,
145
+ includeRelations: true,
146
+ cursor: '0001:T10609',
147
+ limit: 50,
148
+ }),
149
+ ).toEqual({
150
+ rootId: 'T10545',
151
+ direction: 'descendants',
152
+ maxDepth: 2,
153
+ includeRelations: true,
154
+ cursor: '0001:T10609',
155
+ limit: 50,
156
+ });
157
+
158
+ expect(
159
+ workGraphSliceSchema.parse({
160
+ rootId: 'T10545',
161
+ direction: 'descendants',
162
+ nodes: [taskNode],
163
+ edges: [{ fromId: 'T10545', toId: 'T10609', kind: 'contains' }],
164
+ pageInfo: emptyPageInfo,
165
+ }).nodes,
166
+ ).toHaveLength(1);
167
+
168
+ expect(
169
+ workGraphReadinessParamsSchema.parse({
170
+ rootId: 'T10545',
171
+ role: 'worker',
172
+ includeGateBlockers: true,
173
+ }),
174
+ ).toEqual({ rootId: 'T10545', role: 'worker', includeGateBlockers: true });
175
+
176
+ expect(
177
+ workGraphReadinessResultSchema.parse({
178
+ rootId: 'T10545',
179
+ role: 'worker',
180
+ ready: true,
181
+ warnings: [],
182
+ groups: readyGroups,
183
+ }).ready,
184
+ ).toBe(true);
185
+ });
186
+
187
+ it('validates scaffold validate/apply contracts and hierarchy diagnostics', () => {
188
+ const nodes = [
189
+ { id: 'T10545', type: 'epic' },
190
+ { id: 'T10609', type: 'task', parentId: 'T10545' },
191
+ ] as const;
192
+ const edges = [
193
+ {
194
+ source: 'dependency',
195
+ fromId: 'T10609',
196
+ toId: 'T10608',
197
+ kind: 'depends_on',
198
+ },
199
+ ] as const;
200
+
201
+ expect(
202
+ workGraphScaffoldValidateParamsSchema.parse({ rootId: 'T10545', nodes, edges, dryRun: true }),
203
+ ).toEqual({ rootId: 'T10545', nodes, edges, dryRun: true });
204
+
205
+ expect(
206
+ workGraphScaffoldValidateResultSchema.parse({
207
+ rootId: 'T10545',
208
+ valid: true,
209
+ dryRun: true,
210
+ issues: [{ code: 'W_EXISTING_NODE', message: 'Node already exists', severity: 'warning' }],
211
+ hierarchy: { valid: true, violations: [] },
212
+ }).valid,
213
+ ).toBe(true);
214
+
215
+ expect(
216
+ workGraphScaffoldApplyParamsSchema.parse({ rootId: 'T10545', nodes, edges, apply: true }),
217
+ ).toEqual({ rootId: 'T10545', nodes, edges, apply: true });
218
+
219
+ expect(
220
+ workGraphScaffoldApplyResultSchema.parse({
221
+ rootId: 'T10545',
222
+ valid: true,
223
+ dryRun: false,
224
+ applied: true,
225
+ nodesChanged: 2,
226
+ edgesChanged: 1,
227
+ issues: [],
228
+ hierarchy: { valid: true, violations: [] },
229
+ }).edgesChanged,
230
+ ).toBe(1);
231
+ });
232
+ });