@cleocode/contracts 2026.5.111 → 2026.5.113
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__/cli-meta-suggested-next.test.d.ts +21 -0
- package/dist/__tests__/cli-meta-suggested-next.test.d.ts.map +1 -0
- package/dist/__tests__/cli-meta-suggested-next.test.js +70 -0
- package/dist/__tests__/cli-meta-suggested-next.test.js.map +1 -0
- package/dist/__tests__/evidence-atom-schema.test.d.ts +16 -0
- package/dist/__tests__/evidence-atom-schema.test.d.ts.map +1 -0
- package/dist/__tests__/evidence-atom-schema.test.js +394 -0
- package/dist/__tests__/evidence-atom-schema.test.js.map +1 -0
- package/dist/config/__tests__/manifest.test.d.ts +13 -0
- package/dist/config/__tests__/manifest.test.d.ts.map +1 -0
- package/dist/config/__tests__/manifest.test.js +143 -0
- package/dist/config/__tests__/manifest.test.js.map +1 -0
- package/dist/config/manifest.d.ts +158 -0
- package/dist/config/manifest.d.ts.map +1 -0
- package/dist/config/manifest.js +123 -0
- package/dist/config/manifest.js.map +1 -0
- package/dist/docs/__tests__/provenance.test.d.ts +14 -0
- package/dist/docs/__tests__/provenance.test.d.ts.map +1 -0
- package/dist/docs/__tests__/provenance.test.js +579 -0
- package/dist/docs/__tests__/provenance.test.js.map +1 -0
- package/dist/docs/provenance.d.ts +731 -0
- package/dist/docs/provenance.d.ts.map +1 -0
- package/dist/docs/provenance.js +258 -0
- package/dist/docs/provenance.js.map +1 -0
- package/dist/docs-taxonomy.d.ts +24 -0
- package/dist/docs-taxonomy.d.ts.map +1 -1
- package/dist/docs-taxonomy.js +27 -0
- package/dist/docs-taxonomy.js.map +1 -1
- package/dist/doctor.d.ts +101 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -1
- package/dist/evidence-atom-schema.d.ts +407 -0
- package/dist/evidence-atom-schema.d.ts.map +1 -0
- package/dist/evidence-atom-schema.js +505 -0
- package/dist/evidence-atom-schema.js.map +1 -0
- package/dist/exit-codes.d.ts +37 -0
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +37 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/invariants/__tests__/adr-056-release.test.d.ts +16 -0
- package/dist/invariants/__tests__/adr-056-release.test.d.ts.map +1 -0
- package/dist/invariants/__tests__/adr-056-release.test.js +88 -0
- package/dist/invariants/__tests__/adr-056-release.test.js.map +1 -0
- package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts +22 -0
- package/dist/invariants/__tests__/adr-070-orchestration.test.d.ts.map +1 -0
- package/dist/invariants/__tests__/adr-070-orchestration.test.js +151 -0
- package/dist/invariants/__tests__/adr-070-orchestration.test.js.map +1 -0
- package/dist/invariants/__tests__/registry.test.d.ts +14 -0
- package/dist/invariants/__tests__/registry.test.d.ts.map +1 -0
- package/dist/invariants/__tests__/registry.test.js +113 -0
- package/dist/invariants/__tests__/registry.test.js.map +1 -0
- package/dist/invariants/adr-056-release.d.ts +42 -0
- package/dist/invariants/adr-056-release.d.ts.map +1 -0
- package/dist/invariants/adr-056-release.js +143 -0
- package/dist/invariants/adr-056-release.js.map +1 -0
- package/dist/invariants/adr-070-orchestration.d.ts +73 -0
- package/dist/invariants/adr-070-orchestration.d.ts.map +1 -0
- package/dist/invariants/adr-070-orchestration.js +290 -0
- package/dist/invariants/adr-070-orchestration.js.map +1 -0
- package/dist/invariants/adr-073-saga.d.ts +32 -0
- package/dist/invariants/adr-073-saga.d.ts.map +1 -0
- package/dist/invariants/adr-073-saga.js +155 -0
- package/dist/invariants/adr-073-saga.js.map +1 -0
- package/dist/invariants/index.d.ts +162 -0
- package/dist/invariants/index.d.ts.map +1 -0
- package/dist/invariants/index.js +118 -0
- package/dist/invariants/index.js.map +1 -0
- package/dist/operations/__tests__/input-contract.test.d.ts +20 -0
- package/dist/operations/__tests__/input-contract.test.d.ts.map +1 -0
- package/dist/operations/__tests__/input-contract.test.js +158 -0
- package/dist/operations/__tests__/input-contract.test.js.map +1 -0
- package/dist/operations/docs.d.ts +16 -0
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/input-contract.d.ts +267 -0
- package/dist/operations/input-contract.d.ts.map +1 -0
- package/dist/operations/input-contract.js +29 -0
- package/dist/operations/input-contract.js.map +1 -0
- package/dist/templates/__tests__/manifest.test.d.ts +18 -0
- package/dist/templates/__tests__/manifest.test.d.ts.map +1 -0
- package/dist/templates/__tests__/manifest.test.js +123 -0
- package/dist/templates/__tests__/manifest.test.js.map +1 -0
- package/dist/templates/manifest.d.ts +186 -0
- package/dist/templates/manifest.d.ts.map +1 -0
- package/dist/templates/manifest.js +165 -0
- package/dist/templates/manifest.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/cli-meta-suggested-next.test.ts +76 -0
- package/src/__tests__/evidence-atom-schema.test.ts +499 -0
- package/src/config/__tests__/manifest.test.ts +176 -0
- package/src/config/manifest.ts +218 -0
- package/src/docs/__tests__/__snapshots__/provenance.test.ts.snap +252 -0
- package/src/docs/__tests__/provenance.test.ts +648 -0
- package/src/docs/provenance.ts +601 -0
- package/src/docs-taxonomy.ts +55 -0
- package/src/doctor.ts +111 -0
- package/src/errors.ts +4 -0
- package/src/evidence-atom-schema.ts +628 -0
- package/src/exit-codes.ts +39 -0
- package/src/index.ts +129 -0
- package/src/invariants/__tests__/adr-056-release.test.ts +102 -0
- package/src/invariants/__tests__/adr-070-orchestration.test.ts +186 -0
- package/src/invariants/__tests__/registry.test.ts +136 -0
- package/src/invariants/adr-056-release.ts +154 -0
- package/src/invariants/adr-070-orchestration.ts +317 -0
- package/src/invariants/adr-073-saga.ts +167 -0
- package/src/invariants/index.ts +222 -0
- package/src/operations/__tests__/input-contract.test.ts +188 -0
- package/src/operations/docs.ts +16 -0
- package/src/operations/input-contract.ts +301 -0
- package/src/templates/__tests__/manifest.test.ts +140 -0
- package/src/templates/manifest.ts +208 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit + snapshot tests for the Docs Provenance Graph contract (T10166).
|
|
3
|
+
*
|
|
4
|
+
* Coverage:
|
|
5
|
+
* - Round-trip Zod parse for each node variant + edge variant + envelope.
|
|
6
|
+
* - Discriminator rejection for unknown `kind` / `relation` / lifecycle values.
|
|
7
|
+
* - Type-guard narrowing for all five node variants.
|
|
8
|
+
* - Snapshot test for a typical-sized graph (5 docs, 8 tasks, 4 decisions,
|
|
9
|
+
* 2 sessions) — locks the wire shape consumed by the renderer.
|
|
10
|
+
*
|
|
11
|
+
* @task T10166
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it } from 'vitest';
|
|
15
|
+
import {
|
|
16
|
+
type DocProvenanceResponse,
|
|
17
|
+
docLifecycleStatusSchema,
|
|
18
|
+
docProvenanceResponseSchema,
|
|
19
|
+
isDocProvenanceResponse,
|
|
20
|
+
isProvenanceDecisionNode,
|
|
21
|
+
isProvenanceDocNode,
|
|
22
|
+
isProvenanceMemoryNode,
|
|
23
|
+
isProvenanceSessionNode,
|
|
24
|
+
isProvenanceTaskNode,
|
|
25
|
+
type ProvenanceDecisionNode,
|
|
26
|
+
type ProvenanceDocNode,
|
|
27
|
+
type ProvenanceEdge,
|
|
28
|
+
type ProvenanceMemoryNode,
|
|
29
|
+
type ProvenanceNode,
|
|
30
|
+
type ProvenanceSessionNode,
|
|
31
|
+
type ProvenanceTaskNode,
|
|
32
|
+
provenanceDocNodeSchema,
|
|
33
|
+
provenanceEdgeRelationSchema,
|
|
34
|
+
provenanceEdgeSchema,
|
|
35
|
+
provenanceNodeKindSchema,
|
|
36
|
+
provenanceNodeSchema,
|
|
37
|
+
} from '../provenance.js';
|
|
38
|
+
|
|
39
|
+
// ─── Enum schemas ─────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe('provenanceNodeKindSchema', () => {
|
|
42
|
+
it('accepts every canonical node kind', () => {
|
|
43
|
+
expect(provenanceNodeKindSchema.parse('doc')).toBe('doc');
|
|
44
|
+
expect(provenanceNodeKindSchema.parse('task')).toBe('task');
|
|
45
|
+
expect(provenanceNodeKindSchema.parse('decision')).toBe('decision');
|
|
46
|
+
expect(provenanceNodeKindSchema.parse('session')).toBe('session');
|
|
47
|
+
expect(provenanceNodeKindSchema.parse('memory')).toBe('memory');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects an unknown node kind', () => {
|
|
51
|
+
expect(() => provenanceNodeKindSchema.parse('artifact')).toThrow();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('provenanceEdgeRelationSchema', () => {
|
|
56
|
+
it('accepts every canonical edge relation', () => {
|
|
57
|
+
for (const rel of [
|
|
58
|
+
'attached-to',
|
|
59
|
+
'supersedes',
|
|
60
|
+
'superseded-by',
|
|
61
|
+
'related-task',
|
|
62
|
+
'linked-decision',
|
|
63
|
+
'derived-from',
|
|
64
|
+
] as const) {
|
|
65
|
+
expect(provenanceEdgeRelationSchema.parse(rel)).toBe(rel);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects an unknown edge relation', () => {
|
|
70
|
+
expect(() => provenanceEdgeRelationSchema.parse('mentions')).toThrow();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('docLifecycleStatusSchema', () => {
|
|
75
|
+
it('accepts every canonical lifecycle status', () => {
|
|
76
|
+
for (const s of ['active', 'superseded', 'archived', 'draft'] as const) {
|
|
77
|
+
expect(docLifecycleStatusSchema.parse(s)).toBe(s);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('rejects an unknown lifecycle status', () => {
|
|
82
|
+
expect(() => docLifecycleStatusSchema.parse('retired')).toThrow();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ─── Node variants ────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
describe('provenanceDocNodeSchema', () => {
|
|
89
|
+
it('round-trips a minimal doc node', () => {
|
|
90
|
+
const node: ProvenanceDocNode = {
|
|
91
|
+
kind: 'doc',
|
|
92
|
+
id: 'adr-078-docs-provenance-graph',
|
|
93
|
+
slug: 'adr-078-docs-provenance-graph',
|
|
94
|
+
docKind: 'adr',
|
|
95
|
+
title: 'ADR 078 — Docs Provenance Graph',
|
|
96
|
+
lifecycleStatus: 'active',
|
|
97
|
+
publishedAt: '2026-05-22T18:00:00.000Z',
|
|
98
|
+
};
|
|
99
|
+
const parsed = provenanceDocNodeSchema.parse(node);
|
|
100
|
+
expect(parsed).toEqual(node);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('round-trips a superseded doc node with optional fields', () => {
|
|
104
|
+
const node: ProvenanceDocNode = {
|
|
105
|
+
kind: 'doc',
|
|
106
|
+
id: 'adr-066-task-creation-axes',
|
|
107
|
+
slug: 'adr-066-task-creation-axes',
|
|
108
|
+
docKind: 'adr',
|
|
109
|
+
title: 'ADR 066 — Task Creation Axes',
|
|
110
|
+
lifecycleStatus: 'superseded',
|
|
111
|
+
publishedAt: '2026-03-01T00:00:00.000Z',
|
|
112
|
+
supersededAt: '2026-05-22T18:00:00.000Z',
|
|
113
|
+
summary: 'Original 3-axis task creation model',
|
|
114
|
+
metadata: { author: 'cleo-prime' },
|
|
115
|
+
};
|
|
116
|
+
const parsed = provenanceDocNodeSchema.parse(node);
|
|
117
|
+
expect(parsed).toEqual(node);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('rejects a doc node with the wrong discriminator', () => {
|
|
121
|
+
expect(() =>
|
|
122
|
+
provenanceDocNodeSchema.parse({
|
|
123
|
+
kind: 'task',
|
|
124
|
+
id: 'T1',
|
|
125
|
+
title: 't',
|
|
126
|
+
slug: 's',
|
|
127
|
+
docKind: 'adr',
|
|
128
|
+
lifecycleStatus: 'active',
|
|
129
|
+
publishedAt: '2026-05-22T18:00:00.000Z',
|
|
130
|
+
}),
|
|
131
|
+
).toThrow();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('provenanceNodeSchema (discriminated union)', () => {
|
|
136
|
+
it('parses a task node', () => {
|
|
137
|
+
const node: ProvenanceTaskNode = {
|
|
138
|
+
kind: 'task',
|
|
139
|
+
id: 'T10166',
|
|
140
|
+
title: 'C9: Provenance graph contract',
|
|
141
|
+
taskType: 'task',
|
|
142
|
+
status: 'in_progress',
|
|
143
|
+
};
|
|
144
|
+
expect(provenanceNodeSchema.parse(node)).toEqual(node);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('parses a decision node', () => {
|
|
148
|
+
const node: ProvenanceDecisionNode = {
|
|
149
|
+
kind: 'decision',
|
|
150
|
+
id: 'D-arch-001',
|
|
151
|
+
title: 'Adopt evidence-based gate ritual',
|
|
152
|
+
outcome: 'accepted',
|
|
153
|
+
decidedAt: '2026-04-12T14:30:00.000Z',
|
|
154
|
+
};
|
|
155
|
+
expect(provenanceNodeSchema.parse(node)).toEqual(node);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('parses a session node', () => {
|
|
159
|
+
const node: ProvenanceSessionNode = {
|
|
160
|
+
kind: 'session',
|
|
161
|
+
id: 'ses_2026-05-22_abc123',
|
|
162
|
+
title: 'Session 2026-05-22 — T10166 implementation',
|
|
163
|
+
startedAt: '2026-05-22T18:00:00.000Z',
|
|
164
|
+
};
|
|
165
|
+
expect(provenanceNodeSchema.parse(node)).toEqual(node);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('parses a memory node', () => {
|
|
169
|
+
const node: ProvenanceMemoryNode = {
|
|
170
|
+
kind: 'memory',
|
|
171
|
+
id: 'O-2026-05-22-001',
|
|
172
|
+
title: 'Observed spawn contention under parallel sessions',
|
|
173
|
+
memoryType: 'observation',
|
|
174
|
+
recordedAt: '2026-05-22T19:15:00.000Z',
|
|
175
|
+
};
|
|
176
|
+
expect(provenanceNodeSchema.parse(node)).toEqual(node);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('rejects an unknown discriminator', () => {
|
|
180
|
+
expect(() =>
|
|
181
|
+
provenanceNodeSchema.parse({
|
|
182
|
+
kind: 'artifact',
|
|
183
|
+
id: 'a-1',
|
|
184
|
+
title: 'unknown',
|
|
185
|
+
}),
|
|
186
|
+
).toThrow();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// ─── Edges ────────────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
describe('provenanceEdgeSchema', () => {
|
|
193
|
+
it('round-trips a minimal edge', () => {
|
|
194
|
+
const edge: ProvenanceEdge = {
|
|
195
|
+
relation: 'attached-to',
|
|
196
|
+
from: 'adr-078-docs-provenance-graph',
|
|
197
|
+
fromKind: 'doc',
|
|
198
|
+
to: 'T10157',
|
|
199
|
+
toKind: 'task',
|
|
200
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
201
|
+
};
|
|
202
|
+
expect(provenanceEdgeSchema.parse(edge)).toEqual(edge);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('round-trips an edge with optional summary', () => {
|
|
206
|
+
const edge: ProvenanceEdge = {
|
|
207
|
+
relation: 'supersedes',
|
|
208
|
+
from: 'adr-078-docs-provenance-graph',
|
|
209
|
+
fromKind: 'doc',
|
|
210
|
+
to: 'adr-066-task-creation-axes',
|
|
211
|
+
toKind: 'doc',
|
|
212
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
213
|
+
summary: 'replaces 3-axis model with full cross-entity graph',
|
|
214
|
+
};
|
|
215
|
+
expect(provenanceEdgeSchema.parse(edge)).toEqual(edge);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('rejects an edge with an unknown relation', () => {
|
|
219
|
+
expect(() =>
|
|
220
|
+
provenanceEdgeSchema.parse({
|
|
221
|
+
relation: 'mentions',
|
|
222
|
+
from: 'a',
|
|
223
|
+
fromKind: 'doc',
|
|
224
|
+
to: 'b',
|
|
225
|
+
toKind: 'doc',
|
|
226
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
227
|
+
}),
|
|
228
|
+
).toThrow();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// ─── Envelope ─────────────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
describe('docProvenanceResponseSchema', () => {
|
|
235
|
+
it('validates an empty graph', () => {
|
|
236
|
+
const empty: DocProvenanceResponse = {
|
|
237
|
+
nodes: [],
|
|
238
|
+
edges: [],
|
|
239
|
+
totalNodes: 0,
|
|
240
|
+
totalEdges: 0,
|
|
241
|
+
};
|
|
242
|
+
expect(docProvenanceResponseSchema.parse(empty)).toEqual(empty);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('validates a single-node graph', () => {
|
|
246
|
+
const single: DocProvenanceResponse = {
|
|
247
|
+
nodes: [
|
|
248
|
+
{
|
|
249
|
+
kind: 'doc',
|
|
250
|
+
id: 'adr-078-docs-provenance-graph',
|
|
251
|
+
slug: 'adr-078-docs-provenance-graph',
|
|
252
|
+
docKind: 'adr',
|
|
253
|
+
title: 'ADR 078',
|
|
254
|
+
lifecycleStatus: 'active',
|
|
255
|
+
publishedAt: '2026-05-22T18:00:00.000Z',
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
edges: [],
|
|
259
|
+
totalNodes: 1,
|
|
260
|
+
totalEdges: 0,
|
|
261
|
+
};
|
|
262
|
+
expect(docProvenanceResponseSchema.parse(single)).toEqual(single);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('rejects negative totals', () => {
|
|
266
|
+
expect(() =>
|
|
267
|
+
docProvenanceResponseSchema.parse({
|
|
268
|
+
nodes: [],
|
|
269
|
+
edges: [],
|
|
270
|
+
totalNodes: -1,
|
|
271
|
+
totalEdges: 0,
|
|
272
|
+
}),
|
|
273
|
+
).toThrow();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('isDocProvenanceResponse narrows a valid envelope', () => {
|
|
277
|
+
const candidate: unknown = {
|
|
278
|
+
nodes: [],
|
|
279
|
+
edges: [],
|
|
280
|
+
totalNodes: 0,
|
|
281
|
+
totalEdges: 0,
|
|
282
|
+
};
|
|
283
|
+
expect(isDocProvenanceResponse(candidate)).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('isDocProvenanceResponse rejects a non-object', () => {
|
|
287
|
+
expect(isDocProvenanceResponse(null)).toBe(false);
|
|
288
|
+
expect(isDocProvenanceResponse('graph')).toBe(false);
|
|
289
|
+
expect(isDocProvenanceResponse({ nodes: 'oops' })).toBe(false);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ─── Type guards ──────────────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
describe('type guards', () => {
|
|
296
|
+
const doc: ProvenanceDocNode = {
|
|
297
|
+
kind: 'doc',
|
|
298
|
+
id: 'adr-078-docs-provenance-graph',
|
|
299
|
+
slug: 'adr-078-docs-provenance-graph',
|
|
300
|
+
docKind: 'adr',
|
|
301
|
+
title: 'ADR 078',
|
|
302
|
+
lifecycleStatus: 'active',
|
|
303
|
+
publishedAt: '2026-05-22T18:00:00.000Z',
|
|
304
|
+
};
|
|
305
|
+
const task: ProvenanceTaskNode = {
|
|
306
|
+
kind: 'task',
|
|
307
|
+
id: 'T10166',
|
|
308
|
+
title: 'C9',
|
|
309
|
+
taskType: 'task',
|
|
310
|
+
status: 'in_progress',
|
|
311
|
+
};
|
|
312
|
+
const decision: ProvenanceDecisionNode = {
|
|
313
|
+
kind: 'decision',
|
|
314
|
+
id: 'D-arch-001',
|
|
315
|
+
title: 'Adopt EBG ritual',
|
|
316
|
+
outcome: 'accepted',
|
|
317
|
+
decidedAt: '2026-04-12T14:30:00.000Z',
|
|
318
|
+
};
|
|
319
|
+
const session: ProvenanceSessionNode = {
|
|
320
|
+
kind: 'session',
|
|
321
|
+
id: 'ses_x',
|
|
322
|
+
title: 's',
|
|
323
|
+
startedAt: '2026-05-22T18:00:00.000Z',
|
|
324
|
+
};
|
|
325
|
+
const memory: ProvenanceMemoryNode = {
|
|
326
|
+
kind: 'memory',
|
|
327
|
+
id: 'O-1',
|
|
328
|
+
title: 'm',
|
|
329
|
+
memoryType: 'observation',
|
|
330
|
+
recordedAt: '2026-05-22T18:00:00.000Z',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
it('isProvenanceDocNode narrows correctly', () => {
|
|
334
|
+
expect(isProvenanceDocNode(doc)).toBe(true);
|
|
335
|
+
expect(isProvenanceDocNode(task)).toBe(false);
|
|
336
|
+
expect(isProvenanceDocNode(decision)).toBe(false);
|
|
337
|
+
expect(isProvenanceDocNode(session)).toBe(false);
|
|
338
|
+
expect(isProvenanceDocNode(memory)).toBe(false);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('isProvenanceTaskNode narrows correctly', () => {
|
|
342
|
+
expect(isProvenanceTaskNode(task)).toBe(true);
|
|
343
|
+
expect(isProvenanceTaskNode(doc)).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('isProvenanceDecisionNode narrows correctly', () => {
|
|
347
|
+
expect(isProvenanceDecisionNode(decision)).toBe(true);
|
|
348
|
+
expect(isProvenanceDecisionNode(doc)).toBe(false);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('isProvenanceSessionNode narrows correctly', () => {
|
|
352
|
+
expect(isProvenanceSessionNode(session)).toBe(true);
|
|
353
|
+
expect(isProvenanceSessionNode(doc)).toBe(false);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('isProvenanceMemoryNode narrows correctly', () => {
|
|
357
|
+
expect(isProvenanceMemoryNode(memory)).toBe(true);
|
|
358
|
+
expect(isProvenanceMemoryNode(doc)).toBe(false);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ─── Snapshot: typical-sized graph (AC4) ──────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
describe('typical-sized graph (AC4)', () => {
|
|
365
|
+
/**
|
|
366
|
+
* Build the AC4 fixture: 5 docs, 8 tasks, 4 decisions, 2 sessions.
|
|
367
|
+
* Memory nodes are not part of the AC4 fixture but the contract supports
|
|
368
|
+
* them — see {@link provenanceMemoryNodeSchema} tests above.
|
|
369
|
+
*/
|
|
370
|
+
const buildFixture = (): DocProvenanceResponse => {
|
|
371
|
+
const docs: ProvenanceDocNode[] = [
|
|
372
|
+
{
|
|
373
|
+
kind: 'doc',
|
|
374
|
+
id: 'adr-078-docs-provenance-graph',
|
|
375
|
+
slug: 'adr-078-docs-provenance-graph',
|
|
376
|
+
docKind: 'adr',
|
|
377
|
+
title: 'ADR 078 — Docs Provenance Graph',
|
|
378
|
+
lifecycleStatus: 'active',
|
|
379
|
+
publishedAt: '2026-05-22T18:00:00.000Z',
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
kind: 'doc',
|
|
383
|
+
id: 'spec-t10157-e12-provenance',
|
|
384
|
+
slug: 'spec-t10157-e12-provenance',
|
|
385
|
+
docKind: 'spec',
|
|
386
|
+
title: 'E12 Docs Provenance Specification',
|
|
387
|
+
lifecycleStatus: 'active',
|
|
388
|
+
publishedAt: '2026-05-22T18:05:00.000Z',
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
kind: 'doc',
|
|
392
|
+
id: 'research-provenance-graph-design',
|
|
393
|
+
slug: 'research-provenance-graph-design',
|
|
394
|
+
docKind: 'research',
|
|
395
|
+
title: 'Research — Cross-entity provenance graph design',
|
|
396
|
+
lifecycleStatus: 'active',
|
|
397
|
+
publishedAt: '2026-05-21T10:00:00.000Z',
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
kind: 'doc',
|
|
401
|
+
id: 'adr-066-task-creation-axes',
|
|
402
|
+
slug: 'adr-066-task-creation-axes',
|
|
403
|
+
docKind: 'adr',
|
|
404
|
+
title: 'ADR 066 — Task Creation Axes',
|
|
405
|
+
lifecycleStatus: 'superseded',
|
|
406
|
+
publishedAt: '2026-03-01T00:00:00.000Z',
|
|
407
|
+
supersededAt: '2026-05-22T18:00:00.000Z',
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
kind: 'doc',
|
|
411
|
+
id: 'handoff-sg-template-config-ssot-session-1',
|
|
412
|
+
slug: 'handoff-sg-template-config-ssot-session-1',
|
|
413
|
+
docKind: 'handoff',
|
|
414
|
+
title: 'SG-TEMPLATE-CONFIG-SSOT handoff — session 1',
|
|
415
|
+
lifecycleStatus: 'active',
|
|
416
|
+
publishedAt: '2026-05-22T19:30:00.000Z',
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
const tasks: ProvenanceTaskNode[] = [
|
|
420
|
+
{ kind: 'task', id: 'T9855', title: 'Saga T9855', taskType: 'saga', status: 'in_progress' },
|
|
421
|
+
{
|
|
422
|
+
kind: 'task',
|
|
423
|
+
id: 'T10157',
|
|
424
|
+
title: 'E12 — Provenance Graph',
|
|
425
|
+
taskType: 'epic',
|
|
426
|
+
status: 'in_progress',
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
kind: 'task',
|
|
430
|
+
id: 'T10158',
|
|
431
|
+
title: 'C1 — attachments-table migration',
|
|
432
|
+
taskType: 'task',
|
|
433
|
+
status: 'done',
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
kind: 'task',
|
|
437
|
+
id: 'T10162',
|
|
438
|
+
title: 'C5 — supersede verb',
|
|
439
|
+
taskType: 'task',
|
|
440
|
+
status: 'pending',
|
|
441
|
+
},
|
|
442
|
+
{ kind: 'task', id: 'T10164', title: 'C7 — graph CLI', taskType: 'task', status: 'pending' },
|
|
443
|
+
{
|
|
444
|
+
kind: 'task',
|
|
445
|
+
id: 'T10166',
|
|
446
|
+
title: 'C9 — provenance graph contract',
|
|
447
|
+
taskType: 'task',
|
|
448
|
+
status: 'in_progress',
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
kind: 'task',
|
|
452
|
+
id: 'T10138',
|
|
453
|
+
title: 'TreeResponse contract',
|
|
454
|
+
taskType: 'task',
|
|
455
|
+
status: 'done',
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
kind: 'task',
|
|
459
|
+
id: 'T10166-sub1',
|
|
460
|
+
title: 'Snapshot test fixture',
|
|
461
|
+
taskType: 'subtask',
|
|
462
|
+
status: 'in_progress',
|
|
463
|
+
},
|
|
464
|
+
];
|
|
465
|
+
const decisions: ProvenanceDecisionNode[] = [
|
|
466
|
+
{
|
|
467
|
+
kind: 'decision',
|
|
468
|
+
id: 'D-prov-001',
|
|
469
|
+
title: 'Cross-entity graph (not doc-only)',
|
|
470
|
+
outcome: 'accepted',
|
|
471
|
+
decidedAt: '2026-05-21T12:00:00.000Z',
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
kind: 'decision',
|
|
475
|
+
id: 'D-prov-002',
|
|
476
|
+
title: 'Discriminated unions over inheritance',
|
|
477
|
+
outcome: 'accepted',
|
|
478
|
+
decidedAt: '2026-05-21T13:00:00.000Z',
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
kind: 'decision',
|
|
482
|
+
id: 'D-prov-003',
|
|
483
|
+
title: 'Edge carries fromKind/toKind for disambiguation',
|
|
484
|
+
outcome: 'accepted',
|
|
485
|
+
decidedAt: '2026-05-21T14:00:00.000Z',
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
kind: 'decision',
|
|
489
|
+
id: 'D-prov-004',
|
|
490
|
+
title: 'Compatible with TreeResponse renderer',
|
|
491
|
+
outcome: 'proposed',
|
|
492
|
+
decidedAt: '2026-05-22T18:00:00.000Z',
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
const sessions: ProvenanceSessionNode[] = [
|
|
496
|
+
{
|
|
497
|
+
kind: 'session',
|
|
498
|
+
id: 'ses_2026-05-21_research',
|
|
499
|
+
title: 'Research session — provenance graph design',
|
|
500
|
+
startedAt: '2026-05-21T10:00:00.000Z',
|
|
501
|
+
endedAt: '2026-05-21T14:30:00.000Z',
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
kind: 'session',
|
|
505
|
+
id: 'ses_2026-05-22_impl',
|
|
506
|
+
title: 'Implementation session — T10166',
|
|
507
|
+
startedAt: '2026-05-22T18:00:00.000Z',
|
|
508
|
+
},
|
|
509
|
+
];
|
|
510
|
+
const nodes: ProvenanceNode[] = [...docs, ...tasks, ...decisions, ...sessions];
|
|
511
|
+
const edges: ProvenanceEdge[] = [
|
|
512
|
+
// ADR-078 supersedes ADR-066
|
|
513
|
+
{
|
|
514
|
+
relation: 'supersedes',
|
|
515
|
+
from: 'adr-078-docs-provenance-graph',
|
|
516
|
+
fromKind: 'doc',
|
|
517
|
+
to: 'adr-066-task-creation-axes',
|
|
518
|
+
toKind: 'doc',
|
|
519
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
relation: 'superseded-by',
|
|
523
|
+
from: 'adr-066-task-creation-axes',
|
|
524
|
+
fromKind: 'doc',
|
|
525
|
+
to: 'adr-078-docs-provenance-graph',
|
|
526
|
+
toKind: 'doc',
|
|
527
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
528
|
+
},
|
|
529
|
+
// ADR-078 attached-to T10157
|
|
530
|
+
{
|
|
531
|
+
relation: 'attached-to',
|
|
532
|
+
from: 'adr-078-docs-provenance-graph',
|
|
533
|
+
fromKind: 'doc',
|
|
534
|
+
to: 'T10157',
|
|
535
|
+
toKind: 'task',
|
|
536
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
537
|
+
},
|
|
538
|
+
// spec attached-to T10157
|
|
539
|
+
{
|
|
540
|
+
relation: 'attached-to',
|
|
541
|
+
from: 'spec-t10157-e12-provenance',
|
|
542
|
+
fromKind: 'doc',
|
|
543
|
+
to: 'T10157',
|
|
544
|
+
toKind: 'task',
|
|
545
|
+
addedAt: '2026-05-22T18:05:00.000Z',
|
|
546
|
+
},
|
|
547
|
+
// research attached-to T10157
|
|
548
|
+
{
|
|
549
|
+
relation: 'attached-to',
|
|
550
|
+
from: 'research-provenance-graph-design',
|
|
551
|
+
fromKind: 'doc',
|
|
552
|
+
to: 'T10157',
|
|
553
|
+
toKind: 'task',
|
|
554
|
+
addedAt: '2026-05-21T10:00:00.000Z',
|
|
555
|
+
},
|
|
556
|
+
// handoff attached-to T9855
|
|
557
|
+
{
|
|
558
|
+
relation: 'attached-to',
|
|
559
|
+
from: 'handoff-sg-template-config-ssot-session-1',
|
|
560
|
+
fromKind: 'doc',
|
|
561
|
+
to: 'T9855',
|
|
562
|
+
toKind: 'task',
|
|
563
|
+
addedAt: '2026-05-22T19:30:00.000Z',
|
|
564
|
+
},
|
|
565
|
+
// T10157 related-task T9855
|
|
566
|
+
{
|
|
567
|
+
relation: 'related-task',
|
|
568
|
+
from: 'T10157',
|
|
569
|
+
fromKind: 'task',
|
|
570
|
+
to: 'T9855',
|
|
571
|
+
toKind: 'task',
|
|
572
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
573
|
+
},
|
|
574
|
+
// T10166 related-task T10138 (TreeResponse compatibility)
|
|
575
|
+
{
|
|
576
|
+
relation: 'related-task',
|
|
577
|
+
from: 'T10166',
|
|
578
|
+
fromKind: 'task',
|
|
579
|
+
to: 'T10138',
|
|
580
|
+
toKind: 'task',
|
|
581
|
+
addedAt: '2026-05-22T18:00:00.000Z',
|
|
582
|
+
summary: 'Compatible with TreeResponse renderer (AC3)',
|
|
583
|
+
},
|
|
584
|
+
// ADR-078 linked-decision D-prov-001
|
|
585
|
+
{
|
|
586
|
+
relation: 'linked-decision',
|
|
587
|
+
from: 'adr-078-docs-provenance-graph',
|
|
588
|
+
fromKind: 'doc',
|
|
589
|
+
to: 'D-prov-001',
|
|
590
|
+
toKind: 'decision',
|
|
591
|
+
addedAt: '2026-05-21T12:00:00.000Z',
|
|
592
|
+
},
|
|
593
|
+
// spec linked-decision D-prov-002
|
|
594
|
+
{
|
|
595
|
+
relation: 'linked-decision',
|
|
596
|
+
from: 'spec-t10157-e12-provenance',
|
|
597
|
+
fromKind: 'doc',
|
|
598
|
+
to: 'D-prov-002',
|
|
599
|
+
toKind: 'decision',
|
|
600
|
+
addedAt: '2026-05-21T13:00:00.000Z',
|
|
601
|
+
},
|
|
602
|
+
// research derived-from session ses_2026-05-21_research
|
|
603
|
+
{
|
|
604
|
+
relation: 'derived-from',
|
|
605
|
+
from: 'research-provenance-graph-design',
|
|
606
|
+
fromKind: 'doc',
|
|
607
|
+
to: 'ses_2026-05-21_research',
|
|
608
|
+
toKind: 'session',
|
|
609
|
+
addedAt: '2026-05-21T10:00:00.000Z',
|
|
610
|
+
},
|
|
611
|
+
// handoff derived-from session ses_2026-05-22_impl
|
|
612
|
+
{
|
|
613
|
+
relation: 'derived-from',
|
|
614
|
+
from: 'handoff-sg-template-config-ssot-session-1',
|
|
615
|
+
fromKind: 'doc',
|
|
616
|
+
to: 'ses_2026-05-22_impl',
|
|
617
|
+
toKind: 'session',
|
|
618
|
+
addedAt: '2026-05-22T19:30:00.000Z',
|
|
619
|
+
},
|
|
620
|
+
];
|
|
621
|
+
return {
|
|
622
|
+
nodes,
|
|
623
|
+
edges,
|
|
624
|
+
totalNodes: nodes.length,
|
|
625
|
+
totalEdges: edges.length,
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
it('matches the AC4 fixture snapshot (5 docs + 8 tasks + 4 decisions + 2 sessions)', () => {
|
|
630
|
+
const graph = buildFixture();
|
|
631
|
+
|
|
632
|
+
// Sanity: AC4 cardinalities exactly.
|
|
633
|
+
expect(graph.nodes.filter((n) => n.kind === 'doc')).toHaveLength(5);
|
|
634
|
+
expect(graph.nodes.filter((n) => n.kind === 'task')).toHaveLength(8);
|
|
635
|
+
expect(graph.nodes.filter((n) => n.kind === 'decision')).toHaveLength(4);
|
|
636
|
+
expect(graph.nodes.filter((n) => n.kind === 'session')).toHaveLength(2);
|
|
637
|
+
expect(graph.totalNodes).toBe(graph.nodes.length);
|
|
638
|
+
expect(graph.totalEdges).toBe(graph.edges.length);
|
|
639
|
+
|
|
640
|
+
// Round-trip through the Zod envelope schema — guarantees the snapshot
|
|
641
|
+
// is also wire-valid.
|
|
642
|
+
const parsed = docProvenanceResponseSchema.parse(graph);
|
|
643
|
+
expect(parsed.totalNodes).toBe(graph.totalNodes);
|
|
644
|
+
expect(parsed.totalEdges).toBe(graph.totalEdges);
|
|
645
|
+
|
|
646
|
+
expect(graph).toMatchSnapshot();
|
|
647
|
+
});
|
|
648
|
+
});
|