agent-inspect 1.7.0 → 1.8.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +11 -6
  3. package/docs/ADAPTER-CONFORMANCE.md +7 -3
  4. package/docs/ADAPTERS.md +120 -5
  5. package/docs/API.md +123 -21
  6. package/docs/CLI.md +154 -6
  7. package/docs/KNOWN-ISSUES.md +7 -1
  8. package/docs/LIMITATIONS.md +7 -1
  9. package/docs/SCHEMA.md +1 -0
  10. package/package.json +12 -2
  11. package/packages/cli/dist/index.cjs +2057 -33
  12. package/packages/cli/dist/index.cjs.map +1 -1
  13. package/packages/cli/dist/index.mjs +2057 -33
  14. package/packages/cli/dist/index.mjs.map +1 -1
  15. package/packages/core/dist/advanced.d.cts +4 -4
  16. package/packages/core/dist/advanced.d.ts +4 -4
  17. package/packages/core/dist/checks.cjs +1535 -0
  18. package/packages/core/dist/checks.cjs.map +1 -0
  19. package/packages/core/dist/checks.d.cts +585 -0
  20. package/packages/core/dist/checks.d.ts +585 -0
  21. package/packages/core/dist/checks.mjs +1512 -0
  22. package/packages/core/dist/checks.mjs.map +1 -0
  23. package/packages/core/dist/diff.d.cts +3 -3
  24. package/packages/core/dist/diff.d.ts +3 -3
  25. package/packages/core/dist/exporters.d.cts +3 -3
  26. package/packages/core/dist/exporters.d.ts +3 -3
  27. package/packages/core/dist/index.d.cts +6 -6
  28. package/packages/core/dist/index.d.ts +6 -6
  29. package/packages/core/dist/{inspect-event-Des4JDHo.d.cts → inspect-event-CevRYp58.d.cts} +1 -1
  30. package/packages/core/dist/{inspect-event-Des4JDHo.d.ts → inspect-event-CevRYp58.d.ts} +1 -1
  31. package/packages/core/dist/{log-config-C1GcJPIM.d.ts → log-config-BPHS4Sds.d.ts} +1 -1
  32. package/packages/core/dist/{log-config-BnH8Ykcb.d.cts → log-config-DanPV3P9.d.cts} +1 -1
  33. package/packages/core/dist/logs.d.cts +3 -3
  34. package/packages/core/dist/logs.d.ts +3 -3
  35. package/packages/core/dist/{persisted-inspect-event-DiFto0K2.d.ts → persisted-inspect-event-Cw7TeYGr.d.ts} +1 -1
  36. package/packages/core/dist/{persisted-inspect-event-0kaRADsp.d.cts → persisted-inspect-event-DHPfzUd8.d.cts} +1 -1
  37. package/packages/core/dist/persisted.d.cts +5 -5
  38. package/packages/core/dist/persisted.d.ts +5 -5
  39. package/packages/core/dist/readers.d.cts +2 -2
  40. package/packages/core/dist/readers.d.ts +2 -2
  41. package/packages/core/dist/{types-tSix7tfv.d.ts → types-Ap9uMdx_.d.ts} +1 -1
  42. package/packages/core/dist/{types-DB8jB6Jg.d.cts → types-B2-BU5CS.d.cts} +1 -1
  43. package/packages/core/dist/writers.d.cts +2 -2
  44. package/packages/core/dist/writers.d.ts +2 -2
@@ -0,0 +1,1512 @@
1
+ // packages/core/src/checks/index.ts
2
+ var SEVERITY_RANK = {
3
+ error: 0,
4
+ warning: 1,
5
+ info: 2
6
+ };
7
+ var STATUS_RANK = {
8
+ fail: 0,
9
+ warning: 1,
10
+ pass: 2
11
+ };
12
+ var CONFIDENCE_RANK = {
13
+ unknown: 0,
14
+ heuristic: 1,
15
+ correlated: 2,
16
+ explicit: 3
17
+ };
18
+ var DEFAULT_SENSITIVE_KEYS = [
19
+ "authorization",
20
+ "cookie",
21
+ "token",
22
+ "apikey",
23
+ "api_key",
24
+ "password",
25
+ "secret",
26
+ "email"
27
+ ];
28
+ var DEFAULT_RAW_CONTENT_KEYS = [
29
+ "body",
30
+ "headers",
31
+ "input",
32
+ "messages",
33
+ "output",
34
+ "payload",
35
+ "prompt",
36
+ "requestbody",
37
+ "request_body",
38
+ "responsebody",
39
+ "response_body",
40
+ "rawprompt",
41
+ "raw_prompt",
42
+ "rawoutput",
43
+ "raw_output",
44
+ "toolinput",
45
+ "tool_input",
46
+ "tooloutput",
47
+ "tool_output"
48
+ ];
49
+ var DEFAULT_SECRET_PATTERNS = [
50
+ { id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
51
+ { id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
52
+ { id: "aws-access-key", pattern: /AKIA[0-9A-Z]{16}/ },
53
+ { id: "github-token", pattern: /gh[opsu]_[A-Za-z0-9_]{20,}/ },
54
+ { id: "key-value-secret", pattern: /(api[_-]?key|token|password|secret)=\S{8,}/i }
55
+ ];
56
+ function compareStrings(a, b) {
57
+ return (a ?? "").localeCompare(b ?? "");
58
+ }
59
+ function diagnostic(code, message, ruleId) {
60
+ return {
61
+ code,
62
+ message,
63
+ severity: "error",
64
+ ...ruleId ? { ruleId } : {}
65
+ };
66
+ }
67
+ function emptySummary() {
68
+ return {
69
+ passed: 0,
70
+ failed: 0,
71
+ warnings: 0,
72
+ errors: 0
73
+ };
74
+ }
75
+ function errorResult(input, diagnostics, selectedRun) {
76
+ return {
77
+ ok: false,
78
+ status: "error",
79
+ format: input.read.format,
80
+ ...selectedRun ? { runId: selectedRun.runId } : {},
81
+ summary: {
82
+ ...emptySummary(),
83
+ errors: diagnostics.filter((item) => item.severity === "error").length
84
+ },
85
+ findings: [],
86
+ diagnostics: [...diagnostics]
87
+ };
88
+ }
89
+ function flattenNodes(nodes) {
90
+ return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
91
+ }
92
+ function buildFacts(input, selectedRun) {
93
+ const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
94
+ const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
95
+ const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
96
+ const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
97
+ const nodesByEventId = /* @__PURE__ */ new Map();
98
+ const childrenByParentId = /* @__PURE__ */ new Map();
99
+ for (const node of nodes) {
100
+ nodesByEventId.set(node.event.eventId, node);
101
+ const parentId = node.event.parentId;
102
+ if (parentId) {
103
+ const children = childrenByParentId.get(parentId) ?? [];
104
+ children.push(node);
105
+ childrenByParentId.set(parentId, children);
106
+ }
107
+ }
108
+ return {
109
+ format: input.read.format,
110
+ runs: Object.freeze([...input.read.runs]),
111
+ events: Object.freeze([...scopedEvents]),
112
+ readerWarnings: Object.freeze([...input.read.warnings]),
113
+ unsupportedFields: Object.freeze([...input.read.unsupportedFields]),
114
+ sourceFiles: Object.freeze([...input.read.sourceFiles]),
115
+ nodesByEventId,
116
+ childrenByParentId,
117
+ rootNodes: Object.freeze(scopedRuns.flatMap((run) => run.children))
118
+ };
119
+ }
120
+ function resolveSelectedRun(input, runId) {
121
+ if (input.selectedRun) {
122
+ if (runId && input.selectedRun.runId !== runId) {
123
+ return {
124
+ diagnostics: [
125
+ diagnostic(
126
+ "AI_CHECK_INVALID_ARGUMENTS",
127
+ `Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
128
+ )
129
+ ]
130
+ };
131
+ }
132
+ return { run: input.selectedRun, diagnostics: [] };
133
+ }
134
+ if (runId) {
135
+ const run = input.read.runs.find((candidate) => candidate.runId === runId);
136
+ if (!run) {
137
+ return {
138
+ diagnostics: [
139
+ diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
140
+ ]
141
+ };
142
+ }
143
+ return { run, diagnostics: [] };
144
+ }
145
+ if (input.read.runs.length === 1) {
146
+ return { run: input.read.runs[0], diagnostics: [] };
147
+ }
148
+ if (input.read.runs.length === 0) {
149
+ return {
150
+ diagnostics: [
151
+ diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", "No runs are available for checks.")
152
+ ]
153
+ };
154
+ }
155
+ return {
156
+ diagnostics: [
157
+ diagnostic(
158
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
159
+ "Multiple runs are available; select a run before executing checks."
160
+ )
161
+ ]
162
+ };
163
+ }
164
+ function selectRules(rules, selectedIds) {
165
+ const diagnostics = [];
166
+ const byId = /* @__PURE__ */ new Map();
167
+ for (const rule of rules) {
168
+ if (byId.has(rule.id)) {
169
+ diagnostics.push(
170
+ diagnostic("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
171
+ );
172
+ continue;
173
+ }
174
+ byId.set(rule.id, rule);
175
+ }
176
+ if (selectedIds && selectedIds.length > 0) {
177
+ const selected = new Set(selectedIds);
178
+ for (const id of selected) {
179
+ if (!byId.has(id)) {
180
+ diagnostics.push(
181
+ diagnostic("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
182
+ );
183
+ }
184
+ }
185
+ return {
186
+ rules: [...byId.values()].filter((rule) => selected.has(rule.id)).sort(compareRules),
187
+ diagnostics
188
+ };
189
+ }
190
+ return { rules: [...byId.values()].sort(compareRules), diagnostics };
191
+ }
192
+ function compareRules(a, b) {
193
+ return a.id.localeCompare(b.id);
194
+ }
195
+ function eventTimestamp(finding, eventById) {
196
+ const eventId = finding.evidence[0]?.eventId;
197
+ return eventId ? eventById.get(eventId)?.timestamp ?? "" : "";
198
+ }
199
+ function compareFindings(eventById) {
200
+ return (a, b) => {
201
+ if (SEVERITY_RANK[a.severity] !== SEVERITY_RANK[b.severity]) {
202
+ return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
203
+ }
204
+ const byRule = a.ruleId.localeCompare(b.ruleId);
205
+ if (byRule !== 0) return byRule;
206
+ if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
207
+ return STATUS_RANK[a.status] - STATUS_RANK[b.status];
208
+ }
209
+ const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
210
+ if (byRun !== 0) return byRun;
211
+ const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
212
+ if (byTime !== 0) return byTime;
213
+ const byEvent = compareStrings(a.evidence[0]?.eventId, b.evidence[0]?.eventId);
214
+ if (byEvent !== 0) return byEvent;
215
+ return compareStrings(a.evidence[0]?.path, b.evidence[0]?.path);
216
+ };
217
+ }
218
+ function normalizeFinding(rule, finding) {
219
+ return {
220
+ ruleId: finding.ruleId || rule.id,
221
+ severity: finding.severity ?? rule.defaultSeverity,
222
+ status: finding.status,
223
+ message: finding.message,
224
+ ...finding.expected !== void 0 ? { expected: finding.expected } : {},
225
+ ...finding.actual !== void 0 ? { actual: finding.actual } : {},
226
+ evidence: [...finding.evidence ?? []]
227
+ };
228
+ }
229
+ function summarize(findings, diagnostics) {
230
+ return {
231
+ passed: findings.filter((finding) => finding.status === "pass").length,
232
+ failed: findings.filter(
233
+ (finding) => finding.status === "fail" && finding.severity === "error"
234
+ ).length,
235
+ warnings: findings.filter(
236
+ (finding) => finding.status === "warning" || finding.severity === "warning"
237
+ ).length,
238
+ errors: diagnostics.filter((item) => item.severity === "error").length
239
+ };
240
+ }
241
+ function stringAttr(event, keys) {
242
+ for (const key of keys) {
243
+ const value = event.attributes?.[key];
244
+ if (typeof value === "string" && value.trim() !== "") return value;
245
+ }
246
+ return void 0;
247
+ }
248
+ function numericAttr(event, keys) {
249
+ for (const key of keys) {
250
+ const value = event.attributes?.[key];
251
+ if (typeof value === "number" && Number.isFinite(value)) return value;
252
+ }
253
+ return void 0;
254
+ }
255
+ function booleanAttr(event, keys) {
256
+ for (const key of keys) {
257
+ const value = event.attributes?.[key];
258
+ if (typeof value === "boolean") return value;
259
+ }
260
+ return void 0;
261
+ }
262
+ function stripPrefix(name, prefixes) {
263
+ for (const prefix of prefixes) {
264
+ if (name.startsWith(prefix)) return name.slice(prefix.length);
265
+ }
266
+ return name;
267
+ }
268
+ function eventEvidence(event, path) {
269
+ return {
270
+ runId: event.runId,
271
+ eventId: event.eventId,
272
+ parentId: event.parentId,
273
+ traceId: event.trace?.traceId,
274
+ spanId: event.trace?.spanId,
275
+ kind: event.kind,
276
+ name: event.name,
277
+ status: event.status,
278
+ ...path ? { path } : {}
279
+ };
280
+ }
281
+ function runEvidence(run) {
282
+ return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
283
+ }
284
+ function failFinding(ruleId, message, evidence, expected, actual) {
285
+ return {
286
+ ruleId,
287
+ severity: "error",
288
+ status: "fail",
289
+ message,
290
+ ...expected !== void 0 ? { expected } : {},
291
+ ...actual !== void 0 ? { actual } : {},
292
+ evidence: [...evidence]
293
+ };
294
+ }
295
+ function toolName(event) {
296
+ return stringAttr(event, ["toolName", "tool"]) ?? stripPrefix(event.name, ["tool:", "function:", "mcp-tools:"]);
297
+ }
298
+ function llmModel(event) {
299
+ return stringAttr(event, ["model", "modelId", "responseModelId", "modelName", "model_name"]) ?? stripPrefix(event.name, ["llm:", "generation:", "transcription:", "speech:"]);
300
+ }
301
+ function llmProvider(event) {
302
+ return stringAttr(event, ["provider", "providerName", "provider_name"]);
303
+ }
304
+ function llmFinishReason(event) {
305
+ return stringAttr(event, ["finishReason", "rawFinishReason", "finish_reason"]);
306
+ }
307
+ function retryCount(event) {
308
+ return numericAttr(event, ["retryCount", "retryAttempt", "retry_attempt", "attempt"]);
309
+ }
310
+ function finishedEvents(context, kind) {
311
+ return context.events.filter(
312
+ (event) => (kind === void 0 || event.kind === kind) && event.status !== "running"
313
+ );
314
+ }
315
+ function firstIndexByName(events) {
316
+ const index = /* @__PURE__ */ new Map();
317
+ for (const [position, event] of events.entries()) {
318
+ const name = toolName(event);
319
+ if (!index.has(name)) index.set(name, position);
320
+ }
321
+ return index;
322
+ }
323
+ function isRecord(value) {
324
+ return typeof value === "object" && value !== null && !Array.isArray(value);
325
+ }
326
+ function eventMap(events) {
327
+ return new Map(events.map((event) => [event.eventId, event]));
328
+ }
329
+ function parseEventTime(value) {
330
+ if (!value) return void 0;
331
+ const parsed = Date.parse(value);
332
+ return Number.isFinite(parsed) ? parsed : void 0;
333
+ }
334
+ function eventStartMs(event) {
335
+ return parseEventTime(event.startedAt) ?? parseEventTime(event.timestamp);
336
+ }
337
+ function eventEndMs(event) {
338
+ const endedAt = parseEventTime(event.endedAt);
339
+ if (endedAt !== void 0) return endedAt;
340
+ const startedAt = eventStartMs(event);
341
+ if (startedAt !== void 0 && event.durationMs !== void 0 && Number.isFinite(event.durationMs)) {
342
+ return startedAt + event.durationMs;
343
+ }
344
+ return void 0;
345
+ }
346
+ function sortedStrings(values) {
347
+ return [...values ?? []].sort((a, b) => a.localeCompare(b));
348
+ }
349
+ function normalizedKey(value) {
350
+ return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
351
+ }
352
+ function lastPathSegment(path) {
353
+ const parts = path.split(".");
354
+ return parts[parts.length - 1] ?? path;
355
+ }
356
+ function valueType(value) {
357
+ if (Array.isArray(value)) return "array";
358
+ if (value === null) return "null";
359
+ return typeof value;
360
+ }
361
+ function serializedByteLength(value) {
362
+ try {
363
+ return Buffer.byteLength(JSON.stringify(value), "utf-8");
364
+ } catch {
365
+ return void 0;
366
+ }
367
+ }
368
+ function pushValueEntries(entries, event, value, path, key, depth = 0) {
369
+ entries.push({ event, path, key, value });
370
+ if (depth >= 8) return;
371
+ if (Array.isArray(value)) {
372
+ for (const [index, item] of value.entries()) {
373
+ pushValueEntries(entries, event, item, `${path}.${index}`, String(index), depth + 1);
374
+ }
375
+ return;
376
+ }
377
+ if (!isRecord(value)) return;
378
+ for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
379
+ pushValueEntries(
380
+ entries,
381
+ event,
382
+ value[nestedKey],
383
+ `${path}.${nestedKey}`,
384
+ nestedKey,
385
+ depth + 1
386
+ );
387
+ }
388
+ }
389
+ function eventValueEntries(event, options = {}) {
390
+ const entries = [];
391
+ if (event.attributes !== void 0) {
392
+ pushValueEntries(entries, event, event.attributes, "attributes", "attributes");
393
+ }
394
+ if (options.includeSummaries) {
395
+ if (event.inputSummary !== void 0) {
396
+ pushValueEntries(entries, event, event.inputSummary, "inputSummary", "inputSummary");
397
+ }
398
+ if (event.outputSummary !== void 0) {
399
+ pushValueEntries(entries, event, event.outputSummary, "outputSummary", "outputSummary");
400
+ }
401
+ }
402
+ if (options.includeError && event.error !== void 0) {
403
+ pushValueEntries(entries, event, event.error, "error", "error");
404
+ }
405
+ return entries;
406
+ }
407
+ function limitFindings(findings, maxFindings) {
408
+ if (maxFindings === void 0 || findings.length <= maxFindings) return findings;
409
+ return findings.slice(0, Math.max(0, maxFindings));
410
+ }
411
+ function hasRedactionMarker(value, markers) {
412
+ return markers.some((marker) => value.includes(marker)) || /^\[HASH:[A-Za-z0-9_-]+\]$/.test(value);
413
+ }
414
+ function isSensitiveKey(key, sensitiveKeys) {
415
+ if (!key) return false;
416
+ const normalized = normalizedKey(key);
417
+ return sensitiveKeys.some((sensitive) => normalized.includes(normalizedKey(sensitive)));
418
+ }
419
+ function isRawContentKey(key, forbiddenKeys) {
420
+ if (!key) return false;
421
+ const normalized = normalizedKey(key);
422
+ return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
423
+ }
424
+ function parentMarkedUnresolved(event) {
425
+ if (booleanAttr(event, [
426
+ "parentUnresolved",
427
+ "unresolvedParent",
428
+ "relationshipUnresolved",
429
+ "unresolvedRelationship"
430
+ ]) === true) {
431
+ return true;
432
+ }
433
+ const resolution = stringAttr(event, [
434
+ "parentResolution",
435
+ "relationshipResolution",
436
+ "relationshipStatus"
437
+ ]);
438
+ return resolution === "unresolved" || resolution === "missing-parent";
439
+ }
440
+ function signalName(event, attributeKeys, prefixes) {
441
+ return stringAttr(event, attributeKeys) ?? stripPrefix(event.name, prefixes);
442
+ }
443
+ function guardrailEvents(context) {
444
+ return finishedEvents2().filter((event) => {
445
+ const name = event.name.toLowerCase();
446
+ if (name.startsWith("guardrail:") || name.includes(".guardrail.")) return true;
447
+ return stringAttr(event, ["guardrailName", "guardrail", "guardrailId"]) !== void 0;
448
+ });
449
+ function finishedEvents2() {
450
+ return context.events.filter((event) => event.status !== "running");
451
+ }
452
+ }
453
+ function retryValue(event) {
454
+ return retryCount(event) ?? 0;
455
+ }
456
+ function eventDurationMs(event) {
457
+ if (event.durationMs !== void 0) return event.durationMs;
458
+ const start = eventStartMs(event);
459
+ const end = eventEndMs(event);
460
+ return start !== void 0 && end !== void 0 && end >= start ? end - start : void 0;
461
+ }
462
+ function treeShape(nodes) {
463
+ const lines = [];
464
+ const visit = (node, path) => {
465
+ lines.push(`${path}:${node.event.kind}:${node.event.name}:${node.event.status ?? "unknown"}`);
466
+ node.children.forEach((child, index) => visit(child, `${path}.${index}`));
467
+ };
468
+ nodes.forEach((node, index) => visit(node, String(index)));
469
+ return lines;
470
+ }
471
+ function statusShape(context) {
472
+ return context.events.map((event) => `${event.kind}:${event.name}:${event.status ?? "unknown"}`).sort((a, b) => a.localeCompare(b));
473
+ }
474
+ function toolShape(context) {
475
+ return finishedEvents(context, "TOOL").map(
476
+ (event) => [
477
+ toolName(event),
478
+ event.status ?? "unknown",
479
+ retryValue(event),
480
+ eventDurationMs(event) ?? "unknown"
481
+ ].join(":")
482
+ );
483
+ }
484
+ function llmShape(context) {
485
+ return finishedEvents(context, "LLM").map(
486
+ (event) => [
487
+ llmProvider(event) ?? "unknown",
488
+ llmModel(event) ?? "unknown",
489
+ llmFinishReason(event) ?? "unknown",
490
+ event.tokenUsage?.input ?? 0,
491
+ event.tokenUsage?.output ?? 0,
492
+ event.tokenUsage?.total ?? 0,
493
+ event.tokenUsage?.cached ?? 0
494
+ ].join(":")
495
+ );
496
+ }
497
+ function errorShape(context) {
498
+ return context.events.filter((event) => event.status === "error" || event.error !== void 0).map(
499
+ (event) => [
500
+ event.kind,
501
+ event.name,
502
+ event.error?.name ?? "Error",
503
+ event.error?.code ?? "unknown"
504
+ ].join(":")
505
+ ).sort((a, b) => a.localeCompare(b));
506
+ }
507
+ function retrievalShape(context) {
508
+ return finishedEvents(context, "RETRIEVER").map(
509
+ (event) => signalName(event, ["retrievalName", "retrieverName", "retriever"], ["retriever:", "retrieval:"])
510
+ ).sort((a, b) => a.localeCompare(b));
511
+ }
512
+ function guardrailShape(context) {
513
+ return guardrailEvents(context).map((event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])).sort((a, b) => a.localeCompare(b));
514
+ }
515
+ function firstEvidenceForKind(context, kind, path) {
516
+ const event = context.events.find((candidate) => candidate.kind === kind);
517
+ return event ? [eventEvidence(event, path)] : runEvidence(context.selectedRun);
518
+ }
519
+ function baselineDiffFinding(message, evidence, expected, actual) {
520
+ return failFinding("baseline.regression", message, evidence, expected, actual);
521
+ }
522
+ function createRunStatusRule(options = {}) {
523
+ const expected = options.expected ?? "ok";
524
+ const allowIncomplete = options.allowIncomplete === true;
525
+ return {
526
+ id: "run.status",
527
+ category: "run",
528
+ defaultSeverity: "error",
529
+ evaluate(context) {
530
+ const findings = [];
531
+ const actual = context.selectedRun?.status ?? "unknown";
532
+ if (actual !== expected) {
533
+ findings.push(
534
+ failFinding(
535
+ "run.status",
536
+ `Run status ${actual} did not match expected ${expected}.`,
537
+ runEvidence(context.selectedRun),
538
+ expected,
539
+ actual
540
+ )
541
+ );
542
+ }
543
+ if (!allowIncomplete) {
544
+ const running = context.events.filter((event) => event.status === "running");
545
+ if (running.length > 0) {
546
+ findings.push(
547
+ failFinding(
548
+ "run.status",
549
+ "Run contains incomplete running events.",
550
+ running.map((event) => eventEvidence(event)),
551
+ "no running events",
552
+ running.length
553
+ )
554
+ );
555
+ }
556
+ }
557
+ return findings;
558
+ }
559
+ };
560
+ }
561
+ function createRunDurationRule(options) {
562
+ return {
563
+ id: "run.duration",
564
+ category: "run",
565
+ defaultSeverity: "error",
566
+ evaluate(context) {
567
+ const actual = context.selectedRun?.durationMs;
568
+ if (actual === void 0 || actual <= options.maxDurationMs) return [];
569
+ return [
570
+ failFinding(
571
+ "run.duration",
572
+ `Run duration ${actual}ms exceeded ${options.maxDurationMs}ms.`,
573
+ runEvidence(context.selectedRun),
574
+ { maxDurationMs: options.maxDurationMs },
575
+ actual
576
+ )
577
+ ];
578
+ }
579
+ };
580
+ }
581
+ function createRunEventCountRule(options) {
582
+ return {
583
+ id: "run.eventCount",
584
+ category: "run",
585
+ defaultSeverity: "error",
586
+ evaluate(context) {
587
+ const count = context.events.filter(
588
+ (event) => options.kind === void 0 || event.kind === options.kind
589
+ ).length;
590
+ const findings = [];
591
+ if (options.min !== void 0 && count < options.min) {
592
+ findings.push(
593
+ failFinding(
594
+ "run.eventCount",
595
+ `Event count ${count} was below minimum ${options.min}.`,
596
+ runEvidence(context.selectedRun),
597
+ { kind: options.kind, min: options.min },
598
+ count
599
+ )
600
+ );
601
+ }
602
+ if (options.max !== void 0 && count > options.max) {
603
+ findings.push(
604
+ failFinding(
605
+ "run.eventCount",
606
+ `Event count ${count} exceeded maximum ${options.max}.`,
607
+ runEvidence(context.selectedRun),
608
+ { kind: options.kind, max: options.max },
609
+ count
610
+ )
611
+ );
612
+ }
613
+ return findings;
614
+ }
615
+ };
616
+ }
617
+ function createRunDepthRule(options) {
618
+ return {
619
+ id: "run.depth",
620
+ category: "run",
621
+ defaultSeverity: "error",
622
+ evaluate(context) {
623
+ const nodes = [...context.nodesByEventId.values()];
624
+ const maxDepth = nodes.reduce((max, node) => Math.max(max, node.depth), 0);
625
+ if (maxDepth <= options.maxDepth) return [];
626
+ const deepest = nodes.filter((node) => node.depth === maxDepth);
627
+ return [
628
+ failFinding(
629
+ "run.depth",
630
+ `Run depth ${maxDepth} exceeded ${options.maxDepth}.`,
631
+ deepest.map((node) => ({
632
+ runId: node.event.runId,
633
+ eventId: node.event.eventId,
634
+ parentId: node.event.parentId,
635
+ kind: node.event.kind,
636
+ name: node.event.name,
637
+ status: node.event.status
638
+ })),
639
+ { maxDepth: options.maxDepth },
640
+ maxDepth
641
+ )
642
+ ];
643
+ }
644
+ };
645
+ }
646
+ function createToolUsageRule(options) {
647
+ return {
648
+ id: "tool.usage",
649
+ category: "tool",
650
+ defaultSeverity: "error",
651
+ evaluate(context) {
652
+ const tools = finishedEvents(context, "TOOL");
653
+ const names = tools.map(toolName);
654
+ const nameSet = new Set(names);
655
+ const findings = [];
656
+ for (const required of options.required ?? []) {
657
+ if (!nameSet.has(required)) {
658
+ findings.push(
659
+ failFinding("tool.usage", `Required tool ${required} did not appear.`, runEvidence(context.selectedRun), required, names)
660
+ );
661
+ }
662
+ }
663
+ const forbidden = new Set(options.forbidden ?? []);
664
+ const allowed = options.allowed ? new Set(options.allowed) : void 0;
665
+ for (const event of tools) {
666
+ const name = toolName(event);
667
+ if (forbidden.has(name)) {
668
+ findings.push(
669
+ failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence(event)], "tool absent", name)
670
+ );
671
+ }
672
+ if (allowed && !allowed.has(name)) {
673
+ findings.push(
674
+ failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence(event)], [...allowed].sort(), name)
675
+ );
676
+ }
677
+ }
678
+ if (options.minCount !== void 0 && tools.length < options.minCount) {
679
+ findings.push(
680
+ failFinding("tool.usage", `Tool count ${tools.length} was below minimum ${options.minCount}.`, runEvidence(context.selectedRun), { minCount: options.minCount }, tools.length)
681
+ );
682
+ }
683
+ if (options.maxCount !== void 0 && tools.length > options.maxCount) {
684
+ findings.push(
685
+ failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence(event)), { maxCount: options.maxCount }, tools.length)
686
+ );
687
+ }
688
+ return findings;
689
+ }
690
+ };
691
+ }
692
+ function createToolOrderingRule(options) {
693
+ return {
694
+ id: "tool.order",
695
+ category: "tool",
696
+ defaultSeverity: "error",
697
+ evaluate(context) {
698
+ const tools = finishedEvents(context, "TOOL");
699
+ const index = firstIndexByName(tools);
700
+ const beforeIndex = index.get(options.before);
701
+ const afterIndex = index.get(options.after);
702
+ if (beforeIndex === void 0 || afterIndex === void 0 || beforeIndex < afterIndex) {
703
+ return [];
704
+ }
705
+ return [
706
+ failFinding(
707
+ "tool.order",
708
+ `Tool ${options.before} must appear before ${options.after}.`,
709
+ [eventEvidence(tools[beforeIndex]), eventEvidence(tools[afterIndex])],
710
+ { before: options.before, after: options.after },
711
+ tools.map(toolName)
712
+ )
713
+ ];
714
+ }
715
+ };
716
+ }
717
+ function createToolFailureRule(options) {
718
+ return {
719
+ id: "tool.failures",
720
+ category: "tool",
721
+ defaultSeverity: "error",
722
+ evaluate(context) {
723
+ const tools = finishedEvents(context, "TOOL");
724
+ const failures = tools.filter((event) => event.status === "error");
725
+ const retries = tools.map((event) => ({ event, count: retryCount(event) })).filter((item) => item.count !== void 0);
726
+ const findings = [];
727
+ if (options.maxFailures !== void 0 && failures.length > options.maxFailures) {
728
+ findings.push(
729
+ failFinding(
730
+ "tool.failures",
731
+ `Tool failure count ${failures.length} exceeded ${options.maxFailures}.`,
732
+ failures.map((event) => eventEvidence(event)),
733
+ { maxFailures: options.maxFailures },
734
+ failures.length
735
+ )
736
+ );
737
+ }
738
+ if (options.maxRetries !== void 0) {
739
+ const excessiveRetries = retries.filter((item) => item.count > options.maxRetries);
740
+ if (excessiveRetries.length > 0) {
741
+ findings.push(
742
+ failFinding(
743
+ "tool.failures",
744
+ `Tool retry count exceeded ${options.maxRetries}.`,
745
+ excessiveRetries.map((item) => eventEvidence(item.event, "attributes.retryCount")),
746
+ { maxRetries: options.maxRetries },
747
+ excessiveRetries.map((item) => ({ tool: toolName(item.event), retries: item.count }))
748
+ )
749
+ );
750
+ }
751
+ }
752
+ return findings;
753
+ }
754
+ };
755
+ }
756
+ function createLlmUsageRule(options) {
757
+ return {
758
+ id: "llm.usage",
759
+ category: "llm",
760
+ defaultSeverity: "error",
761
+ evaluate(context) {
762
+ const llms = finishedEvents(context, "LLM");
763
+ const findings = [];
764
+ const allowedModels = options.allowedModels ? new Set(options.allowedModels) : void 0;
765
+ const allowedProviders = options.allowedProviders ? new Set(options.allowedProviders) : void 0;
766
+ const finishReasons = options.finishReasons ? new Set(options.finishReasons) : void 0;
767
+ if (options.maxCalls !== void 0 && llms.length > options.maxCalls) {
768
+ findings.push(
769
+ failFinding(
770
+ "llm.usage",
771
+ `LLM call count ${llms.length} exceeded ${options.maxCalls}.`,
772
+ llms.map((event) => eventEvidence(event)),
773
+ { maxCalls: options.maxCalls },
774
+ llms.length
775
+ )
776
+ );
777
+ }
778
+ for (const event of llms) {
779
+ const model = llmModel(event);
780
+ const provider = llmProvider(event);
781
+ const finishReason = llmFinishReason(event);
782
+ if (allowedModels && (!model || !allowedModels.has(model))) {
783
+ findings.push(
784
+ failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
785
+ );
786
+ }
787
+ if (allowedProviders && (!provider || !allowedProviders.has(provider))) {
788
+ findings.push(
789
+ failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
790
+ );
791
+ }
792
+ if (finishReasons && (!finishReason || !finishReasons.has(finishReason))) {
793
+ findings.push(
794
+ failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
795
+ );
796
+ }
797
+ }
798
+ const tokenTotals = llms.reduce(
799
+ (totals, event) => ({
800
+ input: totals.input + (event.tokenUsage?.input ?? 0),
801
+ output: totals.output + (event.tokenUsage?.output ?? 0),
802
+ total: totals.total + (event.tokenUsage?.total ?? 0),
803
+ cached: totals.cached + (event.tokenUsage?.cached ?? 0)
804
+ }),
805
+ { input: 0, output: 0, total: 0, cached: 0 }
806
+ );
807
+ const tokenLimits = [
808
+ ["input", options.maxInputTokens],
809
+ ["output", options.maxOutputTokens],
810
+ ["total", options.maxTotalTokens],
811
+ ["cached", options.maxCachedTokens]
812
+ ];
813
+ for (const [key, limit] of tokenLimits) {
814
+ if (limit !== void 0 && tokenTotals[key] > limit) {
815
+ findings.push(
816
+ failFinding(
817
+ "llm.usage",
818
+ `LLM ${key} token count ${tokenTotals[key]} exceeded ${limit}.`,
819
+ llms.map((event) => eventEvidence(event, `tokenUsage.${key}`)),
820
+ { [`max${key[0].toUpperCase()}${key.slice(1)}Tokens`]: limit },
821
+ tokenTotals[key]
822
+ )
823
+ );
824
+ }
825
+ }
826
+ return findings;
827
+ }
828
+ };
829
+ }
830
+ function createStructureIncompleteRule(options = {}) {
831
+ return {
832
+ id: "structure.incomplete",
833
+ category: "structure",
834
+ defaultSeverity: "error",
835
+ evaluate(context) {
836
+ const findings = [];
837
+ if (!options.allowRunning) {
838
+ const running = context.events.filter((event) => event.status === "running");
839
+ if (running.length > 0) {
840
+ findings.push(
841
+ failFinding(
842
+ "structure.incomplete",
843
+ "Trace contains incomplete running events.",
844
+ running.map((event) => eventEvidence(event, "status")),
845
+ "no running events",
846
+ running.length
847
+ )
848
+ );
849
+ }
850
+ }
851
+ if (options.requireEndedAtForStarted) {
852
+ const missingEndedAt = context.events.filter(
853
+ (event) => event.startedAt !== void 0 && event.endedAt === void 0 && event.status !== "running"
854
+ );
855
+ if (missingEndedAt.length > 0) {
856
+ findings.push(
857
+ failFinding(
858
+ "structure.incomplete",
859
+ "Trace contains events with startedAt but no endedAt.",
860
+ missingEndedAt.map((event) => eventEvidence(event, "endedAt")),
861
+ "endedAt for started events",
862
+ missingEndedAt.length
863
+ )
864
+ );
865
+ }
866
+ }
867
+ return findings;
868
+ }
869
+ };
870
+ }
871
+ function createStructureOrphanRule(options = {}) {
872
+ const allowMarkedUnresolved = options.allowMarkedUnresolved ?? true;
873
+ return {
874
+ id: "structure.orphan",
875
+ category: "structure",
876
+ defaultSeverity: "error",
877
+ evaluate(context) {
878
+ const byId = eventMap(context.events);
879
+ const orphans = context.events.filter((event) => {
880
+ if (!event.parentId || byId.has(event.parentId)) return false;
881
+ return !(allowMarkedUnresolved && parentMarkedUnresolved(event));
882
+ });
883
+ if (orphans.length === 0) return [];
884
+ return [
885
+ failFinding(
886
+ "structure.orphan",
887
+ "Trace contains events whose parentId is not present in the selected run.",
888
+ orphans.map((event) => eventEvidence(event, "parentId")),
889
+ "parentId resolves to an event in the selected run",
890
+ orphans.length
891
+ )
892
+ ];
893
+ }
894
+ };
895
+ }
896
+ function createStructureCycleRule() {
897
+ return {
898
+ id: "structure.cycle",
899
+ category: "structure",
900
+ defaultSeverity: "error",
901
+ evaluate(context) {
902
+ const byId = eventMap(context.events);
903
+ const seenCycles = /* @__PURE__ */ new Set();
904
+ const findings = [];
905
+ for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
906
+ const path = [];
907
+ const seenAt = /* @__PURE__ */ new Map();
908
+ let current = event;
909
+ while (current) {
910
+ const existing = seenAt.get(current.eventId);
911
+ if (existing !== void 0) {
912
+ const cycle = path.slice(existing);
913
+ const key = cycle.map((item) => item.eventId).sort().join("\0");
914
+ if (!seenCycles.has(key)) {
915
+ seenCycles.add(key);
916
+ findings.push(
917
+ failFinding(
918
+ "structure.cycle",
919
+ "Trace contains a parentId cycle.",
920
+ cycle.map((item) => eventEvidence(item, "parentId")),
921
+ "acyclic parentId graph",
922
+ cycle.map((item) => item.eventId).sort()
923
+ )
924
+ );
925
+ }
926
+ break;
927
+ }
928
+ seenAt.set(current.eventId, path.length);
929
+ path.push(current);
930
+ current = current.parentId ? byId.get(current.parentId) : void 0;
931
+ }
932
+ }
933
+ return findings;
934
+ }
935
+ };
936
+ }
937
+ function createStructureRelationshipRule(options = {}) {
938
+ return {
939
+ id: "structure.relationship",
940
+ category: "structure",
941
+ defaultSeverity: "error",
942
+ evaluate(context) {
943
+ const byId = eventMap(context.events);
944
+ const findings = [];
945
+ const minConfidence = options.minConfidence;
946
+ for (const event of context.events) {
947
+ if (minConfidence && CONFIDENCE_RANK[event.confidence] < CONFIDENCE_RANK[minConfidence]) {
948
+ findings.push(
949
+ failFinding(
950
+ "structure.relationship",
951
+ `Event confidence ${event.confidence} is below ${minConfidence}.`,
952
+ [eventEvidence(event, "confidence")],
953
+ { minConfidence },
954
+ event.confidence
955
+ )
956
+ );
957
+ }
958
+ if (!event.parentId) continue;
959
+ if (event.parentId === event.eventId) {
960
+ findings.push(
961
+ failFinding(
962
+ "structure.relationship",
963
+ "Event parentId points to itself.",
964
+ [eventEvidence(event, "parentId")],
965
+ "parentId references a distinct event",
966
+ "self"
967
+ )
968
+ );
969
+ continue;
970
+ }
971
+ const parent = byId.get(event.parentId);
972
+ if (!parent) continue;
973
+ if (options.requireParentBeforeChild) {
974
+ const parentTime = eventStartMs(parent);
975
+ const childTime = eventStartMs(event);
976
+ if (parentTime !== void 0 && childTime !== void 0 && parentTime > childTime) {
977
+ findings.push(
978
+ failFinding(
979
+ "structure.relationship",
980
+ "Parent event starts after child event.",
981
+ [eventEvidence(parent), eventEvidence(event, "parentId")],
982
+ "parent start <= child start",
983
+ { parentEventId: parent.eventId, childEventId: event.eventId }
984
+ )
985
+ );
986
+ }
987
+ }
988
+ if (options.requireTraceParentSpan && parent.trace?.spanId && event.trace) {
989
+ const actual = event.trace.parentSpanId;
990
+ if (actual !== parent.trace.spanId) {
991
+ findings.push(
992
+ failFinding(
993
+ "structure.relationship",
994
+ "Trace parentSpanId does not match parent spanId.",
995
+ [eventEvidence(event, "trace.parentSpanId")],
996
+ { parentSpanId: parent.trace.spanId },
997
+ actual ?? "missing"
998
+ )
999
+ );
1000
+ }
1001
+ }
1002
+ }
1003
+ return findings;
1004
+ }
1005
+ };
1006
+ }
1007
+ function createStructureParallelWidthRule(options) {
1008
+ return {
1009
+ id: "structure.parallelWidth",
1010
+ category: "structure",
1011
+ defaultSeverity: "error",
1012
+ evaluate(context) {
1013
+ const findings = [];
1014
+ const byId = eventMap(context.events);
1015
+ if (options.maxChildren !== void 0) {
1016
+ for (const [parentId, children] of context.childrenByParentId.entries()) {
1017
+ if (children.length <= options.maxChildren) continue;
1018
+ const parent = byId.get(parentId);
1019
+ findings.push(
1020
+ failFinding(
1021
+ "structure.parallelWidth",
1022
+ `Parent ${parentId} has ${children.length} children, exceeding ${options.maxChildren}.`,
1023
+ [
1024
+ ...parent ? [eventEvidence(parent)] : [{ runId: context.selectedRun?.runId, eventId: parentId }],
1025
+ ...children.map((child) => ({
1026
+ runId: child.event.runId,
1027
+ eventId: child.event.eventId,
1028
+ parentId: child.event.parentId,
1029
+ kind: child.event.kind,
1030
+ name: child.event.name,
1031
+ status: child.event.status
1032
+ }))
1033
+ ],
1034
+ { maxChildren: options.maxChildren },
1035
+ children.length
1036
+ )
1037
+ );
1038
+ }
1039
+ }
1040
+ if (options.maxConcurrent !== void 0) {
1041
+ const intervals = context.events.map((event) => ({ event, start: eventStartMs(event), end: eventEndMs(event) })).filter(
1042
+ (item) => item.start !== void 0 && item.end !== void 0 && item.end > item.start
1043
+ );
1044
+ const points = intervals.flatMap((item) => [
1045
+ { time: item.start, delta: 1, event: item.event },
1046
+ { time: item.end, delta: -1, event: item.event }
1047
+ ]);
1048
+ points.sort((a, b) => {
1049
+ const byTime = a.time - b.time;
1050
+ if (byTime !== 0) return byTime;
1051
+ const byDelta = a.delta - b.delta;
1052
+ if (byDelta !== 0) return byDelta;
1053
+ return a.event.eventId.localeCompare(b.event.eventId);
1054
+ });
1055
+ const active = /* @__PURE__ */ new Map();
1056
+ let maxActive = [];
1057
+ for (const point of points) {
1058
+ if (point.delta > 0) {
1059
+ active.set(point.event.eventId, point.event);
1060
+ if (active.size > maxActive.length) {
1061
+ maxActive = [...active.values()].sort((a, b) => a.eventId.localeCompare(b.eventId));
1062
+ }
1063
+ } else {
1064
+ active.delete(point.event.eventId);
1065
+ }
1066
+ }
1067
+ if (maxActive.length > options.maxConcurrent) {
1068
+ findings.push(
1069
+ failFinding(
1070
+ "structure.parallelWidth",
1071
+ `Concurrent event width ${maxActive.length} exceeded ${options.maxConcurrent}.`,
1072
+ maxActive.map((event) => eventEvidence(event)),
1073
+ { maxConcurrent: options.maxConcurrent },
1074
+ maxActive.length
1075
+ )
1076
+ );
1077
+ }
1078
+ }
1079
+ return findings;
1080
+ }
1081
+ };
1082
+ }
1083
+ function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
1084
+ return {
1085
+ id: ruleId,
1086
+ category: "structure",
1087
+ defaultSeverity: "error",
1088
+ evaluate(context) {
1089
+ const events = selectEvents(context);
1090
+ const names = events.map(nameForEvent);
1091
+ const nameSet = new Set(names);
1092
+ const findings = [];
1093
+ for (const required of sortedStrings(options.required)) {
1094
+ if (!nameSet.has(required)) {
1095
+ findings.push(
1096
+ failFinding(
1097
+ ruleId,
1098
+ `Required ${label} ${required} did not appear.`,
1099
+ runEvidence(context.selectedRun),
1100
+ required,
1101
+ names
1102
+ )
1103
+ );
1104
+ }
1105
+ }
1106
+ const forbidden = new Set(options.forbidden ?? []);
1107
+ const allowed = options.allowed ? new Set(options.allowed) : void 0;
1108
+ for (const event of events) {
1109
+ const name = nameForEvent(event);
1110
+ if (forbidden.has(name)) {
1111
+ findings.push(
1112
+ failFinding(
1113
+ ruleId,
1114
+ `Forbidden ${label} ${name} appeared.`,
1115
+ [eventEvidence(event)],
1116
+ `${label} absent`,
1117
+ name
1118
+ )
1119
+ );
1120
+ }
1121
+ if (allowed && !allowed.has(name)) {
1122
+ findings.push(
1123
+ failFinding(
1124
+ ruleId,
1125
+ `${label[0].toUpperCase()}${label.slice(1)} ${name} is not in the allowed set.`,
1126
+ [eventEvidence(event)],
1127
+ [...allowed].sort(),
1128
+ name
1129
+ )
1130
+ );
1131
+ }
1132
+ }
1133
+ if (options.minCount !== void 0 && events.length < options.minCount) {
1134
+ findings.push(
1135
+ failFinding(
1136
+ ruleId,
1137
+ `${label[0].toUpperCase()}${label.slice(1)} count ${events.length} was below minimum ${options.minCount}.`,
1138
+ runEvidence(context.selectedRun),
1139
+ { minCount: options.minCount },
1140
+ events.length
1141
+ )
1142
+ );
1143
+ }
1144
+ if (options.maxCount !== void 0 && events.length > options.maxCount) {
1145
+ findings.push(
1146
+ failFinding(
1147
+ ruleId,
1148
+ `${label[0].toUpperCase()}${label.slice(1)} count ${events.length} exceeded maximum ${options.maxCount}.`,
1149
+ events.map((event) => eventEvidence(event)),
1150
+ { maxCount: options.maxCount },
1151
+ events.length
1152
+ )
1153
+ );
1154
+ }
1155
+ return findings;
1156
+ }
1157
+ };
1158
+ }
1159
+ function createRetrievalRule(options) {
1160
+ return createSignalRule(
1161
+ "structure.retrieval",
1162
+ "retrieval",
1163
+ options,
1164
+ (context) => finishedEvents(context, "RETRIEVER"),
1165
+ (event) => signalName(event, ["retrievalName", "retrieverName", "retriever"], ["retriever:", "retrieval:"])
1166
+ );
1167
+ }
1168
+ function createGuardrailRule(options) {
1169
+ return createSignalRule(
1170
+ "structure.guardrail",
1171
+ "guardrail",
1172
+ options,
1173
+ guardrailEvents,
1174
+ (event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])
1175
+ );
1176
+ }
1177
+ function createDecisionRule(options) {
1178
+ return createSignalRule(
1179
+ "structure.decision",
1180
+ "decision",
1181
+ options,
1182
+ (context) => finishedEvents(context, "DECISION"),
1183
+ (event) => signalName(event, ["decisionName", "decision", "decisionId"], ["decision:"])
1184
+ );
1185
+ }
1186
+ function createSafetyRedactionRule(options = {}) {
1187
+ const sensitiveKeys = options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS;
1188
+ const markers = options.redactedMarkers ?? ["[REDACTED]", "[REDACTED:"];
1189
+ return {
1190
+ id: "safety.redaction",
1191
+ category: "safety",
1192
+ defaultSeverity: "error",
1193
+ evaluate(context) {
1194
+ const findings = [];
1195
+ for (const event of context.events) {
1196
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
1197
+ if (!isSensitiveKey(entry.key ?? lastPathSegment(entry.path), sensitiveKeys)) continue;
1198
+ if (typeof entry.value === "string" && hasRedactionMarker(entry.value, markers)) continue;
1199
+ findings.push(
1200
+ failFinding(
1201
+ "safety.redaction",
1202
+ `Sensitive-looking field at ${entry.path} is not redacted.`,
1203
+ [eventEvidence(event, entry.path)],
1204
+ "redaction marker",
1205
+ { path: entry.path, valueType: valueType(entry.value) }
1206
+ )
1207
+ );
1208
+ }
1209
+ }
1210
+ return limitFindings(findings, options.maxFindings);
1211
+ }
1212
+ };
1213
+ }
1214
+ function createSafetyRawContentRule(options = {}) {
1215
+ const forbiddenKeys = options.forbiddenKeys ?? DEFAULT_RAW_CONTENT_KEYS;
1216
+ return {
1217
+ id: "safety.rawPrompt",
1218
+ category: "safety",
1219
+ defaultSeverity: "error",
1220
+ evaluate(context) {
1221
+ const findings = [];
1222
+ for (const event of context.events) {
1223
+ for (const entry of eventValueEntries(event, { includeSummaries: options.includeSummaries })) {
1224
+ const key = entry.key ?? lastPathSegment(entry.path);
1225
+ if (!isRawContentKey(key, forbiddenKeys)) continue;
1226
+ findings.push(
1227
+ failFinding(
1228
+ "safety.rawPrompt",
1229
+ `Raw content-like field ${entry.path} is present.`,
1230
+ [eventEvidence(event, entry.path)],
1231
+ "metadata-only trace fields",
1232
+ { path: entry.path, valueType: valueType(entry.value) }
1233
+ )
1234
+ );
1235
+ }
1236
+ }
1237
+ return limitFindings(findings, options.maxFindings);
1238
+ }
1239
+ };
1240
+ }
1241
+ function createSafetySecretPatternRule(options = {}) {
1242
+ const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
1243
+ const maxStringLength = options.maxStringLength ?? 4096;
1244
+ return {
1245
+ id: "safety.secretPattern",
1246
+ category: "safety",
1247
+ defaultSeverity: "error",
1248
+ evaluate(context) {
1249
+ const findings = [];
1250
+ for (const event of context.events) {
1251
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
1252
+ if (typeof entry.value !== "string") continue;
1253
+ const sample = entry.value.slice(0, maxStringLength);
1254
+ for (const pattern of patterns) {
1255
+ pattern.pattern.lastIndex = 0;
1256
+ if (!pattern.pattern.test(sample)) continue;
1257
+ pattern.pattern.lastIndex = 0;
1258
+ findings.push(
1259
+ failFinding(
1260
+ "safety.secretPattern",
1261
+ `Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
1262
+ [eventEvidence(event, entry.path)],
1263
+ "no secret-like strings",
1264
+ { pattern: pattern.id, path: entry.path }
1265
+ )
1266
+ );
1267
+ break;
1268
+ }
1269
+ }
1270
+ }
1271
+ return limitFindings(findings, options.maxFindings);
1272
+ }
1273
+ };
1274
+ }
1275
+ function createSafetyOversizedAttributeRule(options) {
1276
+ return {
1277
+ id: "safety.oversizedAttribute",
1278
+ category: "safety",
1279
+ defaultSeverity: "error",
1280
+ evaluate(context) {
1281
+ const findings = [];
1282
+ for (const event of context.events) {
1283
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
1284
+ if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
1285
+ findings.push(
1286
+ failFinding(
1287
+ "safety.oversizedAttribute",
1288
+ `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
1289
+ [eventEvidence(event, entry.path)],
1290
+ { maxStringLength: options.maxStringLength },
1291
+ { path: entry.path, length: entry.value.length }
1292
+ )
1293
+ );
1294
+ }
1295
+ if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
1296
+ findings.push(
1297
+ failFinding(
1298
+ "safety.oversizedAttribute",
1299
+ `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
1300
+ [eventEvidence(event, entry.path)],
1301
+ { maxArrayLength: options.maxArrayLength },
1302
+ { path: entry.path, length: entry.value.length }
1303
+ )
1304
+ );
1305
+ }
1306
+ if (isRecord(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
1307
+ findings.push(
1308
+ failFinding(
1309
+ "safety.oversizedAttribute",
1310
+ `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
1311
+ [eventEvidence(event, entry.path)],
1312
+ { maxObjectKeys: options.maxObjectKeys },
1313
+ { path: entry.path, keys: Object.keys(entry.value).length }
1314
+ )
1315
+ );
1316
+ }
1317
+ if (options.maxSerializedBytes !== void 0) {
1318
+ const bytes = serializedByteLength(entry.value);
1319
+ if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
1320
+ findings.push(
1321
+ failFinding(
1322
+ "safety.oversizedAttribute",
1323
+ `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
1324
+ [eventEvidence(event, entry.path)],
1325
+ { maxSerializedBytes: options.maxSerializedBytes },
1326
+ { path: entry.path, bytes }
1327
+ )
1328
+ );
1329
+ }
1330
+ }
1331
+ }
1332
+ }
1333
+ return limitFindings(findings, options.maxFindings);
1334
+ }
1335
+ };
1336
+ }
1337
+ function createBaselineRegressionRule(options) {
1338
+ return {
1339
+ id: "baseline.regression",
1340
+ category: "baseline",
1341
+ defaultSeverity: "error",
1342
+ evaluate(context) {
1343
+ const baselineSelection = resolveSelectedRun(options.baseline, options.baselineRunId);
1344
+ if (baselineSelection.diagnostics.length > 0 || !baselineSelection.run) {
1345
+ return [
1346
+ failFinding(
1347
+ "baseline.regression",
1348
+ "Baseline run could not be selected.",
1349
+ runEvidence(context.selectedRun),
1350
+ "selectable baseline run",
1351
+ baselineSelection.diagnostics.map((item) => item.code)
1352
+ )
1353
+ ];
1354
+ }
1355
+ const baselineFacts = buildFacts(options.baseline, baselineSelection.run);
1356
+ const baselineContext = {
1357
+ ...baselineFacts,
1358
+ selectedRun: baselineSelection.run,
1359
+ sourceLabel: options.baseline.sourceLabel
1360
+ };
1361
+ const findings = [];
1362
+ const durationToleranceMs = options.durationToleranceMs ?? 0;
1363
+ if (options.compareFormat && baselineContext.format !== context.format) {
1364
+ findings.push(
1365
+ baselineDiffFinding(
1366
+ "Trace format differs from baseline.",
1367
+ runEvidence(context.selectedRun),
1368
+ baselineContext.format,
1369
+ context.format
1370
+ )
1371
+ );
1372
+ }
1373
+ const baselineRunStatus = baselineContext.selectedRun?.status ?? "unknown";
1374
+ const candidateRunStatus = context.selectedRun?.status ?? "unknown";
1375
+ if (baselineRunStatus !== candidateRunStatus) {
1376
+ findings.push(
1377
+ baselineDiffFinding(
1378
+ "Run status differs from baseline.",
1379
+ runEvidence(context.selectedRun),
1380
+ baselineRunStatus,
1381
+ candidateRunStatus
1382
+ )
1383
+ );
1384
+ }
1385
+ const baselineDuration = baselineContext.selectedRun?.durationMs;
1386
+ const candidateDuration = context.selectedRun?.durationMs;
1387
+ if (baselineDuration !== void 0 && candidateDuration !== void 0 && Math.abs(candidateDuration - baselineDuration) > durationToleranceMs) {
1388
+ findings.push(
1389
+ baselineDiffFinding(
1390
+ "Run duration differs from baseline beyond tolerance.",
1391
+ runEvidence(context.selectedRun),
1392
+ { durationMs: baselineDuration, toleranceMs: durationToleranceMs },
1393
+ candidateDuration
1394
+ )
1395
+ );
1396
+ }
1397
+ const comparisons = [
1398
+ {
1399
+ label: "Tree shape",
1400
+ path: "tree",
1401
+ expected: treeShape(baselineContext.rootNodes),
1402
+ actual: treeShape(context.rootNodes),
1403
+ evidence: runEvidence(context.selectedRun)
1404
+ },
1405
+ {
1406
+ label: "Event statuses",
1407
+ path: "status",
1408
+ expected: statusShape(baselineContext),
1409
+ actual: statusShape(context),
1410
+ evidence: runEvidence(context.selectedRun)
1411
+ },
1412
+ {
1413
+ label: "Tool usage",
1414
+ path: "tool",
1415
+ expected: toolShape(baselineContext),
1416
+ actual: toolShape(context),
1417
+ evidence: firstEvidenceForKind(context, "TOOL", "tool")
1418
+ },
1419
+ {
1420
+ label: "LLM usage",
1421
+ path: "llm",
1422
+ expected: llmShape(baselineContext),
1423
+ actual: llmShape(context),
1424
+ evidence: firstEvidenceForKind(context, "LLM", "llm")
1425
+ },
1426
+ {
1427
+ label: "Error profile",
1428
+ path: "error",
1429
+ expected: errorShape(baselineContext),
1430
+ actual: errorShape(context),
1431
+ evidence: firstEvidenceForKind(context, "ERROR", "error")
1432
+ },
1433
+ {
1434
+ label: "Retrieval signals",
1435
+ path: "retrieval",
1436
+ expected: retrievalShape(baselineContext),
1437
+ actual: retrievalShape(context),
1438
+ evidence: firstEvidenceForKind(context, "RETRIEVER", "retrieval")
1439
+ },
1440
+ {
1441
+ label: "Guardrail signals",
1442
+ path: "guardrail",
1443
+ expected: guardrailShape(baselineContext),
1444
+ actual: guardrailShape(context),
1445
+ evidence: guardrailEvents(context)[0] ? [eventEvidence(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
1446
+ }
1447
+ ];
1448
+ for (const comparison of comparisons) {
1449
+ if (JSON.stringify(comparison.expected) === JSON.stringify(comparison.actual)) {
1450
+ continue;
1451
+ }
1452
+ findings.push(
1453
+ baselineDiffFinding(
1454
+ `${comparison.label} differs from baseline.`,
1455
+ comparison.evidence.length > 0 ? comparison.evidence : [{ runId: context.selectedRun?.runId, path: comparison.path }],
1456
+ comparison.expected,
1457
+ comparison.actual
1458
+ )
1459
+ );
1460
+ }
1461
+ return findings;
1462
+ }
1463
+ };
1464
+ }
1465
+ function runTraceChecks(input, options = {}) {
1466
+ const selected = resolveSelectedRun(input, options.runId);
1467
+ if (selected.diagnostics.length > 0) {
1468
+ return errorResult(input, selected.diagnostics, selected.run);
1469
+ }
1470
+ const rules = selectRules(options.rules ?? [], options.select);
1471
+ if (rules.diagnostics.length > 0) {
1472
+ return errorResult(input, rules.diagnostics, selected.run);
1473
+ }
1474
+ const facts = buildFacts(input, selected.run);
1475
+ const context = {
1476
+ ...facts,
1477
+ ...selected.run ? { selectedRun: selected.run } : {},
1478
+ ...input.sourceLabel ? { sourceLabel: input.sourceLabel } : {}
1479
+ };
1480
+ const diagnostics = [];
1481
+ const findings = [];
1482
+ for (const rule of rules.rules) {
1483
+ try {
1484
+ findings.push(...rule.evaluate(context).map((finding) => normalizeFinding(rule, finding)));
1485
+ } catch (error) {
1486
+ const message = error instanceof Error ? error.message : String(error);
1487
+ diagnostics.push(
1488
+ diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
1489
+ );
1490
+ }
1491
+ }
1492
+ if (diagnostics.length > 0) {
1493
+ return errorResult(input, diagnostics, selected.run);
1494
+ }
1495
+ const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
1496
+ const sortedFindings = findings.sort(compareFindings(eventById));
1497
+ const summary = summarize(sortedFindings, diagnostics);
1498
+ const status = summary.failed > 0 ? "fail" : "pass";
1499
+ return {
1500
+ ok: status === "pass",
1501
+ status,
1502
+ format: input.read.format,
1503
+ ...selected.run ? { runId: selected.run.runId } : {},
1504
+ summary,
1505
+ findings: sortedFindings,
1506
+ diagnostics
1507
+ };
1508
+ }
1509
+
1510
+ export { createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, runTraceChecks };
1511
+ //# sourceMappingURL=checks.mjs.map
1512
+ //# sourceMappingURL=checks.mjs.map