@cleocode/contracts 2026.4.69 → 2026.4.72

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 (71) hide show
  1. package/dist/__tests__/acceptance-gate.test.d.ts +14 -0
  2. package/dist/__tests__/acceptance-gate.test.d.ts.map +1 -0
  3. package/dist/__tests__/acceptance-gate.test.js +642 -0
  4. package/dist/__tests__/acceptance-gate.test.js.map +1 -0
  5. package/dist/__tests__/attachment.test.d.ts +17 -0
  6. package/dist/__tests__/attachment.test.d.ts.map +1 -0
  7. package/dist/__tests__/attachment.test.js +505 -0
  8. package/dist/__tests__/attachment.test.js.map +1 -0
  9. package/dist/__tests__/evidence-record.test.d.ts +14 -0
  10. package/dist/__tests__/evidence-record.test.d.ts.map +1 -0
  11. package/dist/__tests__/evidence-record.test.js +362 -0
  12. package/dist/__tests__/evidence-record.test.js.map +1 -0
  13. package/dist/__tests__/invariants.test.d.ts +19 -0
  14. package/dist/__tests__/invariants.test.d.ts.map +1 -0
  15. package/dist/__tests__/invariants.test.js +351 -0
  16. package/dist/__tests__/invariants.test.js.map +1 -0
  17. package/dist/acceptance-gate-schema.d.ts +701 -0
  18. package/dist/acceptance-gate-schema.d.ts.map +1 -0
  19. package/dist/acceptance-gate-schema.js +303 -0
  20. package/dist/acceptance-gate-schema.js.map +1 -0
  21. package/dist/acceptance-gate.d.ts +350 -0
  22. package/dist/acceptance-gate.d.ts.map +1 -0
  23. package/dist/acceptance-gate.js +22 -0
  24. package/dist/acceptance-gate.js.map +1 -0
  25. package/dist/attachment-schema.d.ts +243 -0
  26. package/dist/attachment-schema.d.ts.map +1 -0
  27. package/dist/attachment-schema.js +200 -0
  28. package/dist/attachment-schema.js.map +1 -0
  29. package/dist/attachment.d.ts +224 -0
  30. package/dist/attachment.d.ts.map +1 -0
  31. package/dist/attachment.js +21 -0
  32. package/dist/attachment.js.map +1 -0
  33. package/dist/evidence-record-schema.d.ts +202 -0
  34. package/dist/evidence-record-schema.d.ts.map +1 -0
  35. package/dist/evidence-record-schema.js +130 -0
  36. package/dist/evidence-record-schema.js.map +1 -0
  37. package/dist/evidence-record.d.ts +167 -0
  38. package/dist/evidence-record.d.ts.map +1 -0
  39. package/dist/evidence-record.js +21 -0
  40. package/dist/evidence-record.js.map +1 -0
  41. package/dist/index.d.ts +12 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +6 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/task-evidence.d.ts +251 -0
  46. package/dist/task-evidence.d.ts.map +1 -0
  47. package/dist/task-evidence.js +89 -0
  48. package/dist/task-evidence.js.map +1 -0
  49. package/dist/task.d.ts +23 -4
  50. package/dist/task.d.ts.map +1 -1
  51. package/package.json +8 -2
  52. package/schemas/acceptance-gate.schema.json +9 -0
  53. package/schemas/attachment.schema.json +9 -0
  54. package/schemas/gate-result-details.schema.json +9 -0
  55. package/schemas/gate-result.schema.json +9 -0
  56. package/schemas/manifest-v1.json +176 -0
  57. package/schemas/task-evidence.schema.json +9 -0
  58. package/schemas/task.schema.json +9 -0
  59. package/src/__tests__/acceptance-gate.test.ts +736 -0
  60. package/src/__tests__/attachment.test.ts +601 -0
  61. package/src/__tests__/evidence-record.test.ts +412 -0
  62. package/src/__tests__/invariants.test.ts +445 -0
  63. package/src/acceptance-gate-schema.ts +347 -0
  64. package/src/acceptance-gate.ts +370 -0
  65. package/src/attachment-schema.ts +223 -0
  66. package/src/attachment.ts +245 -0
  67. package/src/evidence-record-schema.ts +158 -0
  68. package/src/evidence-record.ts +182 -0
  69. package/src/index.ts +114 -1
  70. package/src/task-evidence.ts +239 -0
  71. package/src/task.ts +24 -4
@@ -0,0 +1,412 @@
1
+ /**
2
+ * Round-trip Zod parse tests for `EvidenceRecord` variants.
3
+ *
4
+ * Each variant is exercised with two cases:
5
+ * 1. A minimal (required-fields-only) input.
6
+ * 2. A maximal (all-fields populated) input with a structural type-check.
7
+ *
8
+ * Five variants × 2 cases = 10 core tests, plus union-routing and rejection tests.
9
+ *
10
+ * @epic T810
11
+ * @task T816
12
+ */
13
+
14
+ import { describe, expect, it } from 'vitest';
15
+ import type {
16
+ CommandOutputRecord,
17
+ EvidenceRecord,
18
+ ImplDiffRecord,
19
+ LintReportRecord,
20
+ TestOutputRecord,
21
+ ValidateSpecCheckRecord,
22
+ } from '../evidence-record.js';
23
+ import {
24
+ commandOutputRecordSchema,
25
+ evidenceRecordSchema,
26
+ implDiffRecordSchema,
27
+ lintReportRecordSchema,
28
+ testOutputRecordSchema,
29
+ validateSpecCheckRecordSchema,
30
+ } from '../evidence-record-schema.js';
31
+
32
+ // ─── Shared fixture helpers ───────────────────────────────────────────────────
33
+
34
+ const SHA256 = 'a'.repeat(64);
35
+ const AGENT = 'T816-worker';
36
+ const TIMESTAMP = '2026-04-16T00:00:00.000Z';
37
+
38
+ // ─── impl-diff ────────────────────────────────────────────────────────────────
39
+
40
+ describe('implDiffRecordSchema', () => {
41
+ it('round-trips a minimal impl-diff record', () => {
42
+ const input = {
43
+ kind: 'impl-diff' as const,
44
+ phase: 'implement' as const,
45
+ agentIdentity: AGENT,
46
+ attachmentSha256: SHA256,
47
+ filesChanged: ['packages/contracts/src/evidence-record.ts'],
48
+ linesAdded: 120,
49
+ linesRemoved: 0,
50
+ ranAt: TIMESTAMP,
51
+ durationMs: 850,
52
+ };
53
+ const result = implDiffRecordSchema.parse(input);
54
+ expect(result.kind).toBe('impl-diff');
55
+ expect(result.phase).toBe('implement');
56
+ expect(result.filesChanged).toHaveLength(1);
57
+ expect(result.linesAdded).toBe(120);
58
+ expect(result.linesRemoved).toBe(0);
59
+ expect(result.durationMs).toBe(850);
60
+ });
61
+
62
+ it('round-trips a maximal impl-diff record and type-checks as EvidenceRecord', () => {
63
+ const input = {
64
+ kind: 'impl-diff' as const,
65
+ phase: 'implement' as const,
66
+ agentIdentity: AGENT,
67
+ attachmentSha256: SHA256,
68
+ filesChanged: [
69
+ 'packages/contracts/src/evidence-record.ts',
70
+ 'packages/contracts/src/evidence-record-schema.ts',
71
+ 'packages/contracts/src/index.ts',
72
+ ],
73
+ linesAdded: 300,
74
+ linesRemoved: 5,
75
+ ranAt: TIMESTAMP,
76
+ durationMs: 1200,
77
+ };
78
+ const result = implDiffRecordSchema.parse(input);
79
+ expect(result.filesChanged).toHaveLength(3);
80
+ expect(result.linesRemoved).toBe(5);
81
+ // Structural compatibility with canonical interface
82
+ const typed: ImplDiffRecord = result;
83
+ expect(typed.kind).toBe('impl-diff');
84
+ // Assignable to the union
85
+ const unionTyped: EvidenceRecord = result;
86
+ expect(unionTyped.kind).toBe('impl-diff');
87
+ });
88
+ });
89
+
90
+ // ─── validate-spec-check ─────────────────────────────────────────────────────
91
+
92
+ describe('validateSpecCheckRecordSchema', () => {
93
+ it('round-trips a minimal validate-spec-check record', () => {
94
+ const input = {
95
+ kind: 'validate-spec-check' as const,
96
+ phase: 'validate' as const,
97
+ agentIdentity: AGENT,
98
+ attachmentSha256: SHA256,
99
+ reqIdsChecked: ['IVTR-01'],
100
+ passed: true,
101
+ details: 'All checked REQ-IDs satisfied.',
102
+ ranAt: TIMESTAMP,
103
+ durationMs: 300,
104
+ };
105
+ const result = validateSpecCheckRecordSchema.parse(input);
106
+ expect(result.kind).toBe('validate-spec-check');
107
+ expect(result.phase).toBe('validate');
108
+ expect(result.passed).toBe(true);
109
+ expect(result.reqIdsChecked).toEqual(['IVTR-01']);
110
+ });
111
+
112
+ it('round-trips a maximal validate-spec-check record with failure', () => {
113
+ const input = {
114
+ kind: 'validate-spec-check' as const,
115
+ phase: 'validate' as const,
116
+ agentIdentity: AGENT,
117
+ attachmentSha256: SHA256,
118
+ reqIdsChecked: ['IVTR-01', 'IVTR-02', 'IVTR-06'],
119
+ passed: false,
120
+ details: 'IVTR-06: EvidenceRecord schema missing lint-report variant.',
121
+ ranAt: TIMESTAMP,
122
+ durationMs: 550,
123
+ };
124
+ const result = validateSpecCheckRecordSchema.parse(input);
125
+ expect(result.reqIdsChecked).toHaveLength(3);
126
+ expect(result.passed).toBe(false);
127
+ expect(result.details).toContain('lint-report');
128
+ // Structural compatibility with canonical interface
129
+ const typed: ValidateSpecCheckRecord = result;
130
+ expect(typed.kind).toBe('validate-spec-check');
131
+ });
132
+ });
133
+
134
+ // ─── test-output ─────────────────────────────────────────────────────────────
135
+
136
+ describe('testOutputRecordSchema', () => {
137
+ it('round-trips a minimal test-output record', () => {
138
+ const input = {
139
+ kind: 'test-output' as const,
140
+ phase: 'test' as const,
141
+ agentIdentity: AGENT,
142
+ attachmentSha256: SHA256,
143
+ command: 'pnpm --filter @cleocode/contracts run test',
144
+ exitCode: 0,
145
+ testsPassed: 10,
146
+ testsFailed: 0,
147
+ ranAt: TIMESTAMP,
148
+ durationMs: 4200,
149
+ };
150
+ const result = testOutputRecordSchema.parse(input);
151
+ expect(result.kind).toBe('test-output');
152
+ expect(result.phase).toBe('test');
153
+ expect(result.exitCode).toBe(0);
154
+ expect(result.testsPassed).toBe(10);
155
+ expect(result.testsFailed).toBe(0);
156
+ });
157
+
158
+ it('round-trips a test-output record with failures and type-checks as EvidenceRecord', () => {
159
+ const input = {
160
+ kind: 'test-output' as const,
161
+ phase: 'test' as const,
162
+ agentIdentity: AGENT,
163
+ attachmentSha256: SHA256,
164
+ command: 'pnpm run test',
165
+ exitCode: 1,
166
+ testsPassed: 8,
167
+ testsFailed: 2,
168
+ ranAt: TIMESTAMP,
169
+ durationMs: 3100,
170
+ };
171
+ const result = testOutputRecordSchema.parse(input);
172
+ expect(result.exitCode).toBe(1);
173
+ expect(result.testsFailed).toBe(2);
174
+ // Structural compatibility
175
+ const typed: TestOutputRecord = result;
176
+ expect(typed.command).toBe('pnpm run test');
177
+ const unionTyped: EvidenceRecord = result;
178
+ expect(unionTyped.kind).toBe('test-output');
179
+ });
180
+ });
181
+
182
+ // ─── lint-report ─────────────────────────────────────────────────────────────
183
+
184
+ describe('lintReportRecordSchema', () => {
185
+ it('round-trips a minimal lint-report record (implement phase)', () => {
186
+ const input = {
187
+ kind: 'lint-report' as const,
188
+ phase: 'implement' as const,
189
+ agentIdentity: AGENT,
190
+ attachmentSha256: SHA256,
191
+ tool: 'biome',
192
+ passed: true,
193
+ warnings: 0,
194
+ errors: 0,
195
+ ranAt: TIMESTAMP,
196
+ durationMs: 620,
197
+ };
198
+ const result = lintReportRecordSchema.parse(input);
199
+ expect(result.kind).toBe('lint-report');
200
+ expect(result.phase).toBe('implement');
201
+ expect(result.tool).toBe('biome');
202
+ expect(result.passed).toBe(true);
203
+ expect(result.warnings).toBe(0);
204
+ expect(result.errors).toBe(0);
205
+ });
206
+
207
+ it('round-trips a lint-report record with warnings (test phase) and type-checks', () => {
208
+ const input = {
209
+ kind: 'lint-report' as const,
210
+ phase: 'test' as const,
211
+ agentIdentity: AGENT,
212
+ attachmentSha256: SHA256,
213
+ tool: 'tsc',
214
+ passed: false,
215
+ warnings: 3,
216
+ errors: 2,
217
+ ranAt: TIMESTAMP,
218
+ durationMs: 800,
219
+ };
220
+ const result = lintReportRecordSchema.parse(input);
221
+ expect(result.phase).toBe('test');
222
+ expect(result.warnings).toBe(3);
223
+ expect(result.errors).toBe(2);
224
+ expect(result.passed).toBe(false);
225
+ // Structural compatibility
226
+ const typed: LintReportRecord = result;
227
+ expect(typed.tool).toBe('tsc');
228
+ const unionTyped: EvidenceRecord = result;
229
+ expect(unionTyped.kind).toBe('lint-report');
230
+ });
231
+ });
232
+
233
+ // ─── command-output ───────────────────────────────────────────────────────────
234
+
235
+ describe('commandOutputRecordSchema', () => {
236
+ it('round-trips a minimal command-output record (implement phase)', () => {
237
+ const input = {
238
+ kind: 'command-output' as const,
239
+ phase: 'implement' as const,
240
+ agentIdentity: AGENT,
241
+ attachmentSha256: SHA256,
242
+ cmd: 'git diff --stat HEAD',
243
+ exitCode: 0,
244
+ ranAt: TIMESTAMP,
245
+ durationMs: 120,
246
+ };
247
+ const result = commandOutputRecordSchema.parse(input);
248
+ expect(result.kind).toBe('command-output');
249
+ expect(result.phase).toBe('implement');
250
+ expect(result.cmd).toBe('git diff --stat HEAD');
251
+ expect(result.exitCode).toBe(0);
252
+ });
253
+
254
+ it('round-trips a command-output record (validate phase) and type-checks as EvidenceRecord', () => {
255
+ const input = {
256
+ kind: 'command-output' as const,
257
+ phase: 'validate' as const,
258
+ agentIdentity: AGENT,
259
+ attachmentSha256: SHA256,
260
+ cmd: 'cleo check --task T816',
261
+ exitCode: 0,
262
+ ranAt: TIMESTAMP,
263
+ durationMs: 250,
264
+ };
265
+ const result = commandOutputRecordSchema.parse(input);
266
+ expect(result.phase).toBe('validate');
267
+ // Structural compatibility
268
+ const typed: CommandOutputRecord = result;
269
+ expect(typed.cmd).toBe('cleo check --task T816');
270
+ const unionTyped: EvidenceRecord = result;
271
+ expect(unionTyped.kind).toBe('command-output');
272
+ });
273
+ });
274
+
275
+ // ─── evidenceRecordSchema (discriminated union routing) ──────────────────────
276
+
277
+ describe('evidenceRecordSchema (discriminated union)', () => {
278
+ it('routes impl-diff by kind', () => {
279
+ const result = evidenceRecordSchema.parse({
280
+ kind: 'impl-diff',
281
+ phase: 'implement',
282
+ agentIdentity: AGENT,
283
+ attachmentSha256: SHA256,
284
+ filesChanged: ['src/x.ts'],
285
+ linesAdded: 10,
286
+ linesRemoved: 2,
287
+ ranAt: TIMESTAMP,
288
+ durationMs: 100,
289
+ });
290
+ expect(result.kind).toBe('impl-diff');
291
+ });
292
+
293
+ it('routes validate-spec-check by kind', () => {
294
+ const result = evidenceRecordSchema.parse({
295
+ kind: 'validate-spec-check',
296
+ phase: 'validate',
297
+ agentIdentity: AGENT,
298
+ attachmentSha256: SHA256,
299
+ reqIdsChecked: ['REQ-01'],
300
+ passed: true,
301
+ details: 'ok',
302
+ ranAt: TIMESTAMP,
303
+ durationMs: 100,
304
+ });
305
+ expect(result.kind).toBe('validate-spec-check');
306
+ });
307
+
308
+ it('routes test-output by kind', () => {
309
+ const result = evidenceRecordSchema.parse({
310
+ kind: 'test-output',
311
+ phase: 'test',
312
+ agentIdentity: AGENT,
313
+ attachmentSha256: SHA256,
314
+ command: 'pnpm test',
315
+ exitCode: 0,
316
+ testsPassed: 5,
317
+ testsFailed: 0,
318
+ ranAt: TIMESTAMP,
319
+ durationMs: 100,
320
+ });
321
+ expect(result.kind).toBe('test-output');
322
+ });
323
+
324
+ it('routes lint-report by kind', () => {
325
+ const result = evidenceRecordSchema.parse({
326
+ kind: 'lint-report',
327
+ phase: 'implement',
328
+ agentIdentity: AGENT,
329
+ attachmentSha256: SHA256,
330
+ tool: 'biome',
331
+ passed: true,
332
+ warnings: 0,
333
+ errors: 0,
334
+ ranAt: TIMESTAMP,
335
+ durationMs: 100,
336
+ });
337
+ expect(result.kind).toBe('lint-report');
338
+ });
339
+
340
+ it('routes command-output by kind', () => {
341
+ const result = evidenceRecordSchema.parse({
342
+ kind: 'command-output',
343
+ phase: 'test',
344
+ agentIdentity: AGENT,
345
+ attachmentSha256: SHA256,
346
+ cmd: 'cleo doctor',
347
+ exitCode: 0,
348
+ ranAt: TIMESTAMP,
349
+ durationMs: 100,
350
+ });
351
+ expect(result.kind).toBe('command-output');
352
+ });
353
+
354
+ it('rejects an unknown kind', () => {
355
+ expect(() =>
356
+ evidenceRecordSchema.parse({
357
+ kind: 'unknown-kind',
358
+ agentIdentity: AGENT,
359
+ attachmentSha256: SHA256,
360
+ ranAt: TIMESTAMP,
361
+ durationMs: 0,
362
+ }),
363
+ ).toThrow();
364
+ });
365
+
366
+ it('rejects an invalid attachmentSha256 (wrong length)', () => {
367
+ expect(() =>
368
+ evidenceRecordSchema.parse({
369
+ kind: 'command-output',
370
+ phase: 'test',
371
+ agentIdentity: AGENT,
372
+ attachmentSha256: 'tooshort',
373
+ cmd: 'cleo doctor',
374
+ exitCode: 0,
375
+ ranAt: TIMESTAMP,
376
+ durationMs: 0,
377
+ }),
378
+ ).toThrow();
379
+ });
380
+
381
+ it('rejects an invalid ranAt (non-datetime string)', () => {
382
+ expect(() =>
383
+ evidenceRecordSchema.parse({
384
+ kind: 'test-output',
385
+ phase: 'test',
386
+ agentIdentity: AGENT,
387
+ attachmentSha256: SHA256,
388
+ command: 'pnpm test',
389
+ exitCode: 0,
390
+ testsPassed: 1,
391
+ testsFailed: 0,
392
+ ranAt: 'not-a-timestamp',
393
+ durationMs: 0,
394
+ }),
395
+ ).toThrow();
396
+ });
397
+
398
+ it('rejects negative durationMs', () => {
399
+ expect(() =>
400
+ evidenceRecordSchema.parse({
401
+ kind: 'command-output',
402
+ phase: 'implement',
403
+ agentIdentity: AGENT,
404
+ attachmentSha256: SHA256,
405
+ cmd: 'cleo doctor',
406
+ exitCode: 0,
407
+ ranAt: TIMESTAMP,
408
+ durationMs: -1,
409
+ }),
410
+ ).toThrow();
411
+ });
412
+ });