@fluxpointstudios/orynq-sdk-process-trace 0.2.0 → 0.3.0

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.
@@ -1,414 +1,420 @@
1
- /**
2
- * @summary Tests for type exports and constants in the process-trace package.
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import {
7
- HASH_DOMAIN_PREFIXES,
8
- DEFAULT_EVENT_VISIBILITY,
9
- } from '../index.js';
10
- import type {
11
- Visibility,
12
- TraceStatus,
13
- SchemaVersion,
14
- TraceEvent,
15
- TraceEventKind,
16
- TraceSpan,
17
- TraceRun,
18
- TraceMerkleTree,
19
- MerkleProof,
20
- TraceBundle,
21
- TraceBundlePublicView,
22
- HashDomain,
23
- RollingHashState,
24
- } from '../index.js';
25
-
26
- describe('HASH_DOMAIN_PREFIXES', () => {
27
- it('has all expected keys', () => {
28
- const expectedKeys: HashDomain[] = [
29
- 'event',
30
- 'roll',
31
- 'span',
32
- 'leaf',
33
- 'node',
34
- 'manifest',
35
- 'root',
36
- ];
37
-
38
- const actualKeys = Object.keys(HASH_DOMAIN_PREFIXES);
39
-
40
- expect(actualKeys).toHaveLength(expectedKeys.length);
41
- for (const key of expectedKeys) {
42
- expect(actualKeys).toContain(key);
43
- }
44
- });
45
-
46
- it('has correct prefix format for all domains', () => {
47
- for (const [domain, prefix] of Object.entries(HASH_DOMAIN_PREFIXES)) {
48
- // All prefixes should follow pattern "poi-trace:<domain>:v1|"
49
- expect(prefix).toMatch(/^poi-trace:[a-z]+:v1\|$/);
50
- expect(prefix).toContain(domain);
51
- }
52
- });
53
-
54
- it('event prefix is correct', () => {
55
- expect(HASH_DOMAIN_PREFIXES.event).toBe('poi-trace:event:v1|');
56
- });
57
-
58
- it('roll prefix is correct', () => {
59
- expect(HASH_DOMAIN_PREFIXES.roll).toBe('poi-trace:roll:v1|');
60
- });
61
-
62
- it('span prefix is correct', () => {
63
- expect(HASH_DOMAIN_PREFIXES.span).toBe('poi-trace:span:v1|');
64
- });
65
-
66
- it('leaf prefix is correct', () => {
67
- expect(HASH_DOMAIN_PREFIXES.leaf).toBe('poi-trace:leaf:v1|');
68
- });
69
-
70
- it('node prefix is correct', () => {
71
- expect(HASH_DOMAIN_PREFIXES.node).toBe('poi-trace:node:v1|');
72
- });
73
-
74
- it('manifest prefix is correct', () => {
75
- expect(HASH_DOMAIN_PREFIXES.manifest).toBe('poi-trace:manifest:v1|');
76
- });
77
-
78
- it('root prefix is correct', () => {
79
- expect(HASH_DOMAIN_PREFIXES.root).toBe('poi-trace:root:v1|');
80
- });
81
-
82
- it('all prefixes are unique', () => {
83
- const prefixes = Object.values(HASH_DOMAIN_PREFIXES);
84
- const uniquePrefixes = new Set(prefixes);
85
- expect(uniquePrefixes.size).toBe(prefixes.length);
86
- });
87
-
88
- it('is immutable (readonly)', () => {
89
- // TypeScript should prevent mutation, but we verify the object shape
90
- expect(typeof HASH_DOMAIN_PREFIXES).toBe('object');
91
- expect(Object.isFrozen(HASH_DOMAIN_PREFIXES)).toBe(false); // It's const but not frozen
92
- });
93
- });
94
-
95
- describe('DEFAULT_EVENT_VISIBILITY', () => {
96
- it('has all expected event kinds', () => {
97
- const expectedKinds: TraceEventKind[] = [
98
- 'command',
99
- 'output',
100
- 'decision',
101
- 'observation',
102
- 'error',
103
- 'custom',
104
- ];
105
-
106
- const actualKinds = Object.keys(DEFAULT_EVENT_VISIBILITY);
107
-
108
- expect(actualKinds).toHaveLength(expectedKinds.length);
109
- for (const kind of expectedKinds) {
110
- expect(actualKinds).toContain(kind);
111
- }
112
- });
113
-
114
- it('command events default to public', () => {
115
- expect(DEFAULT_EVENT_VISIBILITY.command).toBe('public');
116
- });
117
-
118
- it('output events default to private', () => {
119
- expect(DEFAULT_EVENT_VISIBILITY.output).toBe('private');
120
- });
121
-
122
- it('decision events default to private', () => {
123
- expect(DEFAULT_EVENT_VISIBILITY.decision).toBe('private');
124
- });
125
-
126
- it('observation events default to public', () => {
127
- expect(DEFAULT_EVENT_VISIBILITY.observation).toBe('public');
128
- });
129
-
130
- it('error events default to private', () => {
131
- expect(DEFAULT_EVENT_VISIBILITY.error).toBe('private');
132
- });
133
-
134
- it('custom events default to private', () => {
135
- expect(DEFAULT_EVENT_VISIBILITY.custom).toBe('private');
136
- });
137
-
138
- it('all values are valid Visibility types', () => {
139
- const validVisibilities: Visibility[] = ['public', 'private', 'secret'];
140
- for (const visibility of Object.values(DEFAULT_EVENT_VISIBILITY)) {
141
- expect(validVisibilities).toContain(visibility);
142
- }
143
- });
144
-
145
- it('returns expected visibility for each event kind', () => {
146
- const expected: Record<TraceEventKind, Visibility> = {
147
- command: 'public',
148
- output: 'private',
149
- decision: 'private',
150
- observation: 'public',
151
- error: 'private',
152
- custom: 'private',
153
- };
154
-
155
- for (const [kind, visibility] of Object.entries(expected)) {
156
- expect(DEFAULT_EVENT_VISIBILITY[kind as TraceEventKind]).toBe(visibility);
157
- }
158
- });
159
- });
160
-
161
- describe('Visibility type', () => {
162
- it('accepts public visibility', () => {
163
- const visibility: Visibility = 'public';
164
- expect(visibility).toBe('public');
165
- });
166
-
167
- it('accepts private visibility', () => {
168
- const visibility: Visibility = 'private';
169
- expect(visibility).toBe('private');
170
- });
171
-
172
- it('accepts secret visibility', () => {
173
- const visibility: Visibility = 'secret';
174
- expect(visibility).toBe('secret');
175
- });
176
- });
177
-
178
- describe('TraceStatus type', () => {
179
- it('accepts running status', () => {
180
- const status: TraceStatus = 'running';
181
- expect(status).toBe('running');
182
- });
183
-
184
- it('accepts completed status', () => {
185
- const status: TraceStatus = 'completed';
186
- expect(status).toBe('completed');
187
- });
188
-
189
- it('accepts failed status', () => {
190
- const status: TraceStatus = 'failed';
191
- expect(status).toBe('failed');
192
- });
193
-
194
- it('accepts cancelled status', () => {
195
- const status: TraceStatus = 'cancelled';
196
- expect(status).toBe('cancelled');
197
- });
198
- });
199
-
200
- describe('SchemaVersion type', () => {
201
- it('accepts version 1.0', () => {
202
- const version: SchemaVersion = '1.0';
203
- expect(version).toBe('1.0');
204
- });
205
- });
206
-
207
- describe('TraceEvent interface structure', () => {
208
- it('accepts a valid CommandEvent', () => {
209
- const event: TraceEvent = {
210
- kind: 'command',
211
- id: 'test-id',
212
- seq: 0,
213
- timestamp: '2024-01-15T10:30:00.000Z',
214
- visibility: 'public',
215
- command: 'npm install',
216
- args: ['--save-dev'],
217
- };
218
-
219
- expect(event.kind).toBe('command');
220
- expect(event.command).toBe('npm install');
221
- });
222
-
223
- it('accepts a valid OutputEvent', () => {
224
- const event: TraceEvent = {
225
- kind: 'output',
226
- id: 'test-id',
227
- seq: 1,
228
- timestamp: '2024-01-15T10:30:01.000Z',
229
- visibility: 'private',
230
- stream: 'stdout',
231
- content: 'Installation complete',
232
- };
233
-
234
- expect(event.kind).toBe('output');
235
- expect(event.stream).toBe('stdout');
236
- });
237
-
238
- it('accepts a valid DecisionEvent', () => {
239
- const event: TraceEvent = {
240
- kind: 'decision',
241
- id: 'test-id',
242
- seq: 2,
243
- timestamp: '2024-01-15T10:30:02.000Z',
244
- visibility: 'private',
245
- decision: 'Use TypeScript',
246
- reasoning: 'Better type safety',
247
- };
248
-
249
- expect(event.kind).toBe('decision');
250
- expect(event.decision).toBe('Use TypeScript');
251
- });
252
-
253
- it('accepts a valid ObservationEvent', () => {
254
- const event: TraceEvent = {
255
- kind: 'observation',
256
- id: 'test-id',
257
- seq: 3,
258
- timestamp: '2024-01-15T10:30:03.000Z',
259
- visibility: 'public',
260
- observation: 'Package.json exists',
261
- category: 'file-system',
262
- };
263
-
264
- expect(event.kind).toBe('observation');
265
- expect(event.observation).toBe('Package.json exists');
266
- });
267
-
268
- it('accepts a valid ErrorTraceEvent', () => {
269
- const event: TraceEvent = {
270
- kind: 'error',
271
- id: 'test-id',
272
- seq: 4,
273
- timestamp: '2024-01-15T10:30:04.000Z',
274
- visibility: 'private',
275
- error: 'File not found',
276
- code: 'ENOENT',
277
- recoverable: true,
278
- };
279
-
280
- expect(event.kind).toBe('error');
281
- expect(event.error).toBe('File not found');
282
- });
283
-
284
- it('accepts a valid CustomEvent', () => {
285
- const event: TraceEvent = {
286
- kind: 'custom',
287
- id: 'test-id',
288
- seq: 5,
289
- timestamp: '2024-01-15T10:30:05.000Z',
290
- visibility: 'private',
291
- eventType: 'api-call',
292
- data: { endpoint: '/api/v1/users' },
293
- };
294
-
295
- expect(event.kind).toBe('custom');
296
- expect(event.eventType).toBe('api-call');
297
- });
298
- });
299
-
300
- describe('TraceSpan interface structure', () => {
301
- it('accepts a valid TraceSpan', () => {
302
- const span: TraceSpan = {
303
- id: 'span-1',
304
- spanSeq: 0,
305
- name: 'build',
306
- status: 'completed',
307
- visibility: 'public',
308
- startedAt: '2024-01-15T10:30:00.000Z',
309
- endedAt: '2024-01-15T10:31:00.000Z',
310
- durationMs: 60000,
311
- eventIds: ['event-1', 'event-2'],
312
- childSpanIds: [],
313
- hash: 'abc123',
314
- };
315
-
316
- expect(span.id).toBe('span-1');
317
- expect(span.spanSeq).toBe(0);
318
- expect(span.name).toBe('build');
319
- });
320
-
321
- it('accepts span with parent', () => {
322
- const span: TraceSpan = {
323
- id: 'span-2',
324
- spanSeq: 1,
325
- parentSpanId: 'span-1',
326
- name: 'npm-install',
327
- status: 'running',
328
- visibility: 'private',
329
- startedAt: '2024-01-15T10:30:00.000Z',
330
- eventIds: [],
331
- childSpanIds: [],
332
- };
333
-
334
- expect(span.parentSpanId).toBe('span-1');
335
- });
336
-
337
- it('accepts span with metadata', () => {
338
- const span: TraceSpan = {
339
- id: 'span-3',
340
- spanSeq: 2,
341
- name: 'test',
342
- status: 'completed',
343
- visibility: 'public',
344
- startedAt: '2024-01-15T10:30:00.000Z',
345
- eventIds: [],
346
- childSpanIds: [],
347
- metadata: {
348
- environment: 'production',
349
- region: 'us-east-1',
350
- },
351
- };
352
-
353
- expect(span.metadata?.environment).toBe('production');
354
- });
355
- });
356
-
357
- describe('RollingHashState interface structure', () => {
358
- it('accepts a valid RollingHashState', () => {
359
- const state: RollingHashState = {
360
- currentHash: 'abc123def456',
361
- itemCount: 5,
362
- };
363
-
364
- expect(state.currentHash).toBe('abc123def456');
365
- expect(state.itemCount).toBe(5);
366
- });
367
- });
368
-
369
- describe('TraceMerkleTree interface structure', () => {
370
- it('accepts a valid TraceMerkleTree', () => {
371
- const tree: TraceMerkleTree = {
372
- rootHash: 'root123',
373
- leafCount: 3,
374
- depth: 2,
375
- leafHashes: ['leaf1', 'leaf2', 'leaf3'],
376
- };
377
-
378
- expect(tree.rootHash).toBe('root123');
379
- expect(tree.leafCount).toBe(3);
380
- expect(tree.depth).toBe(2);
381
- expect(tree.leafHashes).toHaveLength(3);
382
- });
383
- });
384
-
385
- describe('MerkleProof interface structure', () => {
386
- it('accepts a valid MerkleProof', () => {
387
- const proof: MerkleProof = {
388
- leafHash: 'leaf123',
389
- leafIndex: 1,
390
- siblings: [
391
- { hash: 'sibling1', position: 'left' },
392
- { hash: 'sibling2', position: 'right' },
393
- ],
394
- rootHash: 'root123',
395
- };
396
-
397
- expect(proof.leafHash).toBe('leaf123');
398
- expect(proof.leafIndex).toBe(1);
399
- expect(proof.siblings).toHaveLength(2);
400
- expect(proof.siblings[0]?.position).toBe('left');
401
- expect(proof.rootHash).toBe('root123');
402
- });
403
-
404
- it('accepts empty siblings array', () => {
405
- const proof: MerkleProof = {
406
- leafHash: 'leaf123',
407
- leafIndex: 0,
408
- siblings: [],
409
- rootHash: 'leaf123',
410
- };
411
-
412
- expect(proof.siblings).toHaveLength(0);
413
- });
414
- });
1
+ /**
2
+ * @summary Tests for type exports and constants in the process-trace package.
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import {
7
+ HASH_DOMAIN_PREFIXES,
8
+ DEFAULT_EVENT_VISIBILITY,
9
+ } from '../index.js';
10
+ import type {
11
+ Visibility,
12
+ TraceStatus,
13
+ SchemaVersion,
14
+ TraceEvent,
15
+ TraceEventKind,
16
+ TraceSpan,
17
+ TraceRun,
18
+ TraceMerkleTree,
19
+ MerkleProof,
20
+ TraceBundle,
21
+ TraceBundlePublicView,
22
+ HashDomain,
23
+ RollingHashState,
24
+ } from '../index.js';
25
+
26
+ describe('HASH_DOMAIN_PREFIXES', () => {
27
+ it('has all expected keys', () => {
28
+ const expectedKeys: HashDomain[] = [
29
+ 'event',
30
+ 'roll',
31
+ 'span',
32
+ 'leaf',
33
+ 'node',
34
+ 'manifest',
35
+ 'root',
36
+ 'modelManifest',
37
+ 'governance',
38
+ ];
39
+
40
+ const actualKeys = Object.keys(HASH_DOMAIN_PREFIXES);
41
+
42
+ expect(actualKeys).toHaveLength(expectedKeys.length);
43
+ for (const key of expectedKeys) {
44
+ expect(actualKeys).toContain(key);
45
+ }
46
+ });
47
+
48
+ it('has correct prefix format for all domains', () => {
49
+ for (const [domain, prefix] of Object.entries(HASH_DOMAIN_PREFIXES)) {
50
+ // All prefixes should follow pattern "poi-trace:<domain>:v1|"
51
+ // (domain segment may be kebab-cased, e.g. "model-manifest").
52
+ expect(prefix).toMatch(/^poi-trace:[a-z-]+:v1\|$/);
53
+ // The camelCase key maps to the (possibly kebab-cased) prefix segment.
54
+ expect(prefix.replace(/-/g, '')).toContain(domain.toLowerCase());
55
+ }
56
+ });
57
+
58
+ it('event prefix is correct', () => {
59
+ expect(HASH_DOMAIN_PREFIXES.event).toBe('poi-trace:event:v1|');
60
+ });
61
+
62
+ it('roll prefix is correct', () => {
63
+ expect(HASH_DOMAIN_PREFIXES.roll).toBe('poi-trace:roll:v1|');
64
+ });
65
+
66
+ it('span prefix is correct', () => {
67
+ expect(HASH_DOMAIN_PREFIXES.span).toBe('poi-trace:span:v1|');
68
+ });
69
+
70
+ it('leaf prefix is correct', () => {
71
+ expect(HASH_DOMAIN_PREFIXES.leaf).toBe('poi-trace:leaf:v1|');
72
+ });
73
+
74
+ it('node prefix is correct', () => {
75
+ expect(HASH_DOMAIN_PREFIXES.node).toBe('poi-trace:node:v1|');
76
+ });
77
+
78
+ it('manifest prefix is correct', () => {
79
+ expect(HASH_DOMAIN_PREFIXES.manifest).toBe('poi-trace:manifest:v1|');
80
+ });
81
+
82
+ it('root prefix is correct', () => {
83
+ expect(HASH_DOMAIN_PREFIXES.root).toBe('poi-trace:root:v1|');
84
+ });
85
+
86
+ it('all prefixes are unique', () => {
87
+ const prefixes = Object.values(HASH_DOMAIN_PREFIXES);
88
+ const uniquePrefixes = new Set(prefixes);
89
+ expect(uniquePrefixes.size).toBe(prefixes.length);
90
+ });
91
+
92
+ it('is immutable (readonly)', () => {
93
+ // TypeScript should prevent mutation, but we verify the object shape
94
+ expect(typeof HASH_DOMAIN_PREFIXES).toBe('object');
95
+ expect(Object.isFrozen(HASH_DOMAIN_PREFIXES)).toBe(false); // It's const but not frozen
96
+ });
97
+ });
98
+
99
+ describe('DEFAULT_EVENT_VISIBILITY', () => {
100
+ it('has all expected event kinds', () => {
101
+ const expectedKinds: TraceEventKind[] = [
102
+ 'command',
103
+ 'output',
104
+ 'decision',
105
+ 'observation',
106
+ 'error',
107
+ 'custom',
108
+ 'governance-attestation',
109
+ 'tool-receipt',
110
+ ];
111
+
112
+ const actualKinds = Object.keys(DEFAULT_EVENT_VISIBILITY);
113
+
114
+ expect(actualKinds).toHaveLength(expectedKinds.length);
115
+ for (const kind of expectedKinds) {
116
+ expect(actualKinds).toContain(kind);
117
+ }
118
+ });
119
+
120
+ it('command events default to public', () => {
121
+ expect(DEFAULT_EVENT_VISIBILITY.command).toBe('public');
122
+ });
123
+
124
+ it('output events default to private', () => {
125
+ expect(DEFAULT_EVENT_VISIBILITY.output).toBe('private');
126
+ });
127
+
128
+ it('decision events default to private', () => {
129
+ expect(DEFAULT_EVENT_VISIBILITY.decision).toBe('private');
130
+ });
131
+
132
+ it('observation events default to public', () => {
133
+ expect(DEFAULT_EVENT_VISIBILITY.observation).toBe('public');
134
+ });
135
+
136
+ it('error events default to private', () => {
137
+ expect(DEFAULT_EVENT_VISIBILITY.error).toBe('private');
138
+ });
139
+
140
+ it('custom events default to private', () => {
141
+ expect(DEFAULT_EVENT_VISIBILITY.custom).toBe('private');
142
+ });
143
+
144
+ it('all values are valid Visibility types', () => {
145
+ const validVisibilities: Visibility[] = ['public', 'private', 'secret'];
146
+ for (const visibility of Object.values(DEFAULT_EVENT_VISIBILITY)) {
147
+ expect(validVisibilities).toContain(visibility);
148
+ }
149
+ });
150
+
151
+ it('returns expected visibility for each event kind', () => {
152
+ const expected: Record<TraceEventKind, Visibility> = {
153
+ command: 'public',
154
+ output: 'private',
155
+ decision: 'private',
156
+ observation: 'public',
157
+ error: 'private',
158
+ custom: 'private',
159
+ };
160
+
161
+ for (const [kind, visibility] of Object.entries(expected)) {
162
+ expect(DEFAULT_EVENT_VISIBILITY[kind as TraceEventKind]).toBe(visibility);
163
+ }
164
+ });
165
+ });
166
+
167
+ describe('Visibility type', () => {
168
+ it('accepts public visibility', () => {
169
+ const visibility: Visibility = 'public';
170
+ expect(visibility).toBe('public');
171
+ });
172
+
173
+ it('accepts private visibility', () => {
174
+ const visibility: Visibility = 'private';
175
+ expect(visibility).toBe('private');
176
+ });
177
+
178
+ it('accepts secret visibility', () => {
179
+ const visibility: Visibility = 'secret';
180
+ expect(visibility).toBe('secret');
181
+ });
182
+ });
183
+
184
+ describe('TraceStatus type', () => {
185
+ it('accepts running status', () => {
186
+ const status: TraceStatus = 'running';
187
+ expect(status).toBe('running');
188
+ });
189
+
190
+ it('accepts completed status', () => {
191
+ const status: TraceStatus = 'completed';
192
+ expect(status).toBe('completed');
193
+ });
194
+
195
+ it('accepts failed status', () => {
196
+ const status: TraceStatus = 'failed';
197
+ expect(status).toBe('failed');
198
+ });
199
+
200
+ it('accepts cancelled status', () => {
201
+ const status: TraceStatus = 'cancelled';
202
+ expect(status).toBe('cancelled');
203
+ });
204
+ });
205
+
206
+ describe('SchemaVersion type', () => {
207
+ it('accepts version 1.0', () => {
208
+ const version: SchemaVersion = '1.0';
209
+ expect(version).toBe('1.0');
210
+ });
211
+ });
212
+
213
+ describe('TraceEvent interface structure', () => {
214
+ it('accepts a valid CommandEvent', () => {
215
+ const event: TraceEvent = {
216
+ kind: 'command',
217
+ id: 'test-id',
218
+ seq: 0,
219
+ timestamp: '2024-01-15T10:30:00.000Z',
220
+ visibility: 'public',
221
+ command: 'npm install',
222
+ args: ['--save-dev'],
223
+ };
224
+
225
+ expect(event.kind).toBe('command');
226
+ expect(event.command).toBe('npm install');
227
+ });
228
+
229
+ it('accepts a valid OutputEvent', () => {
230
+ const event: TraceEvent = {
231
+ kind: 'output',
232
+ id: 'test-id',
233
+ seq: 1,
234
+ timestamp: '2024-01-15T10:30:01.000Z',
235
+ visibility: 'private',
236
+ stream: 'stdout',
237
+ content: 'Installation complete',
238
+ };
239
+
240
+ expect(event.kind).toBe('output');
241
+ expect(event.stream).toBe('stdout');
242
+ });
243
+
244
+ it('accepts a valid DecisionEvent', () => {
245
+ const event: TraceEvent = {
246
+ kind: 'decision',
247
+ id: 'test-id',
248
+ seq: 2,
249
+ timestamp: '2024-01-15T10:30:02.000Z',
250
+ visibility: 'private',
251
+ decision: 'Use TypeScript',
252
+ reasoning: 'Better type safety',
253
+ };
254
+
255
+ expect(event.kind).toBe('decision');
256
+ expect(event.decision).toBe('Use TypeScript');
257
+ });
258
+
259
+ it('accepts a valid ObservationEvent', () => {
260
+ const event: TraceEvent = {
261
+ kind: 'observation',
262
+ id: 'test-id',
263
+ seq: 3,
264
+ timestamp: '2024-01-15T10:30:03.000Z',
265
+ visibility: 'public',
266
+ observation: 'Package.json exists',
267
+ category: 'file-system',
268
+ };
269
+
270
+ expect(event.kind).toBe('observation');
271
+ expect(event.observation).toBe('Package.json exists');
272
+ });
273
+
274
+ it('accepts a valid ErrorTraceEvent', () => {
275
+ const event: TraceEvent = {
276
+ kind: 'error',
277
+ id: 'test-id',
278
+ seq: 4,
279
+ timestamp: '2024-01-15T10:30:04.000Z',
280
+ visibility: 'private',
281
+ error: 'File not found',
282
+ code: 'ENOENT',
283
+ recoverable: true,
284
+ };
285
+
286
+ expect(event.kind).toBe('error');
287
+ expect(event.error).toBe('File not found');
288
+ });
289
+
290
+ it('accepts a valid CustomEvent', () => {
291
+ const event: TraceEvent = {
292
+ kind: 'custom',
293
+ id: 'test-id',
294
+ seq: 5,
295
+ timestamp: '2024-01-15T10:30:05.000Z',
296
+ visibility: 'private',
297
+ eventType: 'api-call',
298
+ data: { endpoint: '/api/v1/users' },
299
+ };
300
+
301
+ expect(event.kind).toBe('custom');
302
+ expect(event.eventType).toBe('api-call');
303
+ });
304
+ });
305
+
306
+ describe('TraceSpan interface structure', () => {
307
+ it('accepts a valid TraceSpan', () => {
308
+ const span: TraceSpan = {
309
+ id: 'span-1',
310
+ spanSeq: 0,
311
+ name: 'build',
312
+ status: 'completed',
313
+ visibility: 'public',
314
+ startedAt: '2024-01-15T10:30:00.000Z',
315
+ endedAt: '2024-01-15T10:31:00.000Z',
316
+ durationMs: 60000,
317
+ eventIds: ['event-1', 'event-2'],
318
+ childSpanIds: [],
319
+ hash: 'abc123',
320
+ };
321
+
322
+ expect(span.id).toBe('span-1');
323
+ expect(span.spanSeq).toBe(0);
324
+ expect(span.name).toBe('build');
325
+ });
326
+
327
+ it('accepts span with parent', () => {
328
+ const span: TraceSpan = {
329
+ id: 'span-2',
330
+ spanSeq: 1,
331
+ parentSpanId: 'span-1',
332
+ name: 'npm-install',
333
+ status: 'running',
334
+ visibility: 'private',
335
+ startedAt: '2024-01-15T10:30:00.000Z',
336
+ eventIds: [],
337
+ childSpanIds: [],
338
+ };
339
+
340
+ expect(span.parentSpanId).toBe('span-1');
341
+ });
342
+
343
+ it('accepts span with metadata', () => {
344
+ const span: TraceSpan = {
345
+ id: 'span-3',
346
+ spanSeq: 2,
347
+ name: 'test',
348
+ status: 'completed',
349
+ visibility: 'public',
350
+ startedAt: '2024-01-15T10:30:00.000Z',
351
+ eventIds: [],
352
+ childSpanIds: [],
353
+ metadata: {
354
+ environment: 'production',
355
+ region: 'us-east-1',
356
+ },
357
+ };
358
+
359
+ expect(span.metadata?.environment).toBe('production');
360
+ });
361
+ });
362
+
363
+ describe('RollingHashState interface structure', () => {
364
+ it('accepts a valid RollingHashState', () => {
365
+ const state: RollingHashState = {
366
+ currentHash: 'abc123def456',
367
+ itemCount: 5,
368
+ };
369
+
370
+ expect(state.currentHash).toBe('abc123def456');
371
+ expect(state.itemCount).toBe(5);
372
+ });
373
+ });
374
+
375
+ describe('TraceMerkleTree interface structure', () => {
376
+ it('accepts a valid TraceMerkleTree', () => {
377
+ const tree: TraceMerkleTree = {
378
+ rootHash: 'root123',
379
+ leafCount: 3,
380
+ depth: 2,
381
+ leafHashes: ['leaf1', 'leaf2', 'leaf3'],
382
+ };
383
+
384
+ expect(tree.rootHash).toBe('root123');
385
+ expect(tree.leafCount).toBe(3);
386
+ expect(tree.depth).toBe(2);
387
+ expect(tree.leafHashes).toHaveLength(3);
388
+ });
389
+ });
390
+
391
+ describe('MerkleProof interface structure', () => {
392
+ it('accepts a valid MerkleProof', () => {
393
+ const proof: MerkleProof = {
394
+ leafHash: 'leaf123',
395
+ leafIndex: 1,
396
+ siblings: [
397
+ { hash: 'sibling1', position: 'left' },
398
+ { hash: 'sibling2', position: 'right' },
399
+ ],
400
+ rootHash: 'root123',
401
+ };
402
+
403
+ expect(proof.leafHash).toBe('leaf123');
404
+ expect(proof.leafIndex).toBe(1);
405
+ expect(proof.siblings).toHaveLength(2);
406
+ expect(proof.siblings[0]?.position).toBe('left');
407
+ expect(proof.rootHash).toBe('root123');
408
+ });
409
+
410
+ it('accepts empty siblings array', () => {
411
+ const proof: MerkleProof = {
412
+ leafHash: 'leaf123',
413
+ leafIndex: 0,
414
+ siblings: [],
415
+ rootHash: 'leaf123',
416
+ };
417
+
418
+ expect(proof.siblings).toHaveLength(0);
419
+ });
420
+ });