agent-inspect 4.3.0 → 5.0.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 (102) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/docs/API.md +3 -0
  3. package/docs/CLI.md +37 -0
  4. package/docs/SCHEMA.md +27 -1
  5. package/package.json +1 -1
  6. package/packages/cli/dist/chunk-TB45H3QT.mjs +7881 -0
  7. package/packages/cli/dist/chunk-TB45H3QT.mjs.map +1 -0
  8. package/packages/cli/dist/index.cjs +10461 -9188
  9. package/packages/cli/dist/index.cjs.map +1 -1
  10. package/packages/cli/dist/index.mjs +2619 -5688
  11. package/packages/cli/dist/index.mjs.map +1 -1
  12. package/packages/cli/dist/{src-YFMPWEIS.mjs → src-ZRFSYML6.mjs} +3 -3
  13. package/packages/cli/dist/{src-YFMPWEIS.mjs.map → src-ZRFSYML6.mjs.map} +1 -1
  14. package/packages/core/dist/advanced.cjs +3410 -21
  15. package/packages/core/dist/advanced.cjs.map +1 -1
  16. package/packages/core/dist/advanced.d.cts +147 -9
  17. package/packages/core/dist/advanced.d.ts +147 -9
  18. package/packages/core/dist/advanced.mjs +603 -20
  19. package/packages/core/dist/advanced.mjs.map +1 -1
  20. package/packages/core/dist/checks.cjs +117 -18
  21. package/packages/core/dist/checks.cjs.map +1 -1
  22. package/packages/core/dist/checks.d.cts +11 -3
  23. package/packages/core/dist/checks.d.ts +11 -3
  24. package/packages/core/dist/checks.mjs +7 -1612
  25. package/packages/core/dist/checks.mjs.map +1 -1
  26. package/packages/core/dist/{chunk-2ZXEMWGK.mjs → chunk-3USJVBLA.mjs} +3 -3
  27. package/packages/core/dist/{chunk-2ZXEMWGK.mjs.map → chunk-3USJVBLA.mjs.map} +1 -1
  28. package/packages/core/dist/{chunk-HY7H3CQM.mjs → chunk-CAWPF22J.mjs} +4 -3
  29. package/packages/core/dist/chunk-CAWPF22J.mjs.map +1 -0
  30. package/packages/core/dist/{chunk-XUNJMAPD.mjs → chunk-KNYL56KZ.mjs} +38 -3
  31. package/packages/core/dist/chunk-KNYL56KZ.mjs.map +1 -0
  32. package/packages/core/dist/{chunk-IZBJAZGF.mjs → chunk-P366CCL2.mjs} +34 -3
  33. package/packages/core/dist/chunk-P366CCL2.mjs.map +1 -0
  34. package/packages/core/dist/chunk-QLYOCCEF.mjs +1650 -0
  35. package/packages/core/dist/chunk-QLYOCCEF.mjs.map +1 -0
  36. package/packages/core/dist/{chunk-4JVGFNQH.mjs → chunk-ULCGIRTC.mjs} +95 -8
  37. package/packages/core/dist/chunk-ULCGIRTC.mjs.map +1 -0
  38. package/packages/core/dist/chunk-VFO76UH3.mjs +228 -0
  39. package/packages/core/dist/chunk-VFO76UH3.mjs.map +1 -0
  40. package/packages/core/dist/{chunk-NQZPZ2FN.mjs → chunk-WRE4CNPF.mjs} +6 -3
  41. package/packages/core/dist/chunk-WRE4CNPF.mjs.map +1 -0
  42. package/packages/core/dist/chunk-X2FLDF7M.mjs +1581 -0
  43. package/packages/core/dist/chunk-X2FLDF7M.mjs.map +1 -0
  44. package/packages/core/dist/{chunk-A4V2H3YU.mjs → chunk-ZWA5RWMV.mjs} +40 -4
  45. package/packages/core/dist/chunk-ZWA5RWMV.mjs.map +1 -0
  46. package/packages/core/dist/{context-DjY-jvQk.d.cts → context-BbfkMYjz.d.cts} +5 -3
  47. package/packages/core/dist/{context-BDZmi53V.d.ts → context-Cp8NRbBX.d.ts} +5 -3
  48. package/packages/core/dist/diff.cjs.map +1 -1
  49. package/packages/core/dist/diff.d.cts +3 -3
  50. package/packages/core/dist/diff.d.ts +3 -3
  51. package/packages/core/dist/diff.mjs +1 -1
  52. package/packages/core/dist/exporters.cjs +137 -1
  53. package/packages/core/dist/exporters.cjs.map +1 -1
  54. package/packages/core/dist/exporters.d.cts +6 -3
  55. package/packages/core/dist/exporters.d.ts +6 -3
  56. package/packages/core/dist/exporters.mjs +71 -6
  57. package/packages/core/dist/exporters.mjs.map +1 -1
  58. package/packages/core/dist/index.cjs +181 -6
  59. package/packages/core/dist/index.cjs.map +1 -1
  60. package/packages/core/dist/index.d.cts +14 -7
  61. package/packages/core/dist/index.d.ts +14 -7
  62. package/packages/core/dist/index.mjs +51 -7
  63. package/packages/core/dist/index.mjs.map +1 -1
  64. package/packages/core/dist/{inspect-event-CYAV7Hxm.d.cts → inspect-event-IqwOHbGo.d.cts} +1 -1
  65. package/packages/core/dist/{inspect-event-CYAV7Hxm.d.ts → inspect-event-IqwOHbGo.d.ts} +1 -1
  66. package/packages/core/dist/{log-config-BNQ9UTmP.d.cts → log-config-CW8w5YQ9.d.cts} +1 -1
  67. package/packages/core/dist/{log-config-D3Yp1SZN.d.ts → log-config-SRA4xTp8.d.ts} +1 -1
  68. package/packages/core/dist/logs.d.cts +3 -3
  69. package/packages/core/dist/logs.d.ts +3 -3
  70. package/packages/core/dist/{persisted-inspect-event-BLvb0jSX.d.ts → persisted-inspect-event-DruwqARZ.d.ts} +1 -1
  71. package/packages/core/dist/{persisted-inspect-event-D5SjBz9b.d.cts → persisted-inspect-event-Gd-W8vB7.d.cts} +1 -1
  72. package/packages/core/dist/persisted.cjs +67 -1
  73. package/packages/core/dist/persisted.cjs.map +1 -1
  74. package/packages/core/dist/persisted.d.cts +5 -5
  75. package/packages/core/dist/persisted.d.ts +5 -5
  76. package/packages/core/dist/persisted.mjs +3 -3
  77. package/packages/core/dist/readers.cjs +72 -3
  78. package/packages/core/dist/readers.cjs.map +1 -1
  79. package/packages/core/dist/readers.d.cts +2 -2
  80. package/packages/core/dist/readers.d.ts +2 -2
  81. package/packages/core/dist/readers.mjs +4 -1580
  82. package/packages/core/dist/readers.mjs.map +1 -1
  83. package/packages/core/dist/{types-CQun9LW6.d.ts → types-B1X87dUW.d.cts} +16 -2
  84. package/packages/core/dist/{types-C-llnPH0.d.cts → types-BVkjSd0q.d.ts} +16 -2
  85. package/packages/core/dist/types-CvsKGweN.d.cts +12 -0
  86. package/packages/core/dist/types-CvsKGweN.d.ts +12 -0
  87. package/packages/core/dist/writers.cjs +2 -1
  88. package/packages/core/dist/writers.cjs.map +1 -1
  89. package/packages/core/dist/writers.d.cts +2 -2
  90. package/packages/core/dist/writers.d.ts +2 -2
  91. package/packages/core/dist/writers.mjs +3 -4
  92. package/packages/core/dist/writers.mjs.map +1 -1
  93. package/packages/cli/dist/chunk-BS5LSKZ3.mjs +0 -3671
  94. package/packages/cli/dist/chunk-BS5LSKZ3.mjs.map +0 -1
  95. package/packages/core/dist/chunk-4JVGFNQH.mjs.map +0 -1
  96. package/packages/core/dist/chunk-A4V2H3YU.mjs.map +0 -1
  97. package/packages/core/dist/chunk-EDTQHZPM.mjs +0 -88
  98. package/packages/core/dist/chunk-EDTQHZPM.mjs.map +0 -1
  99. package/packages/core/dist/chunk-HY7H3CQM.mjs.map +0 -1
  100. package/packages/core/dist/chunk-IZBJAZGF.mjs.map +0 -1
  101. package/packages/core/dist/chunk-NQZPZ2FN.mjs.map +0 -1
  102. package/packages/core/dist/chunk-XUNJMAPD.mjs.map +0 -1
@@ -1,1614 +1,9 @@
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 createMaxStepDurationRule(options) {
582
- return {
583
- id: "run.maxStepDuration",
584
- category: "run",
585
- defaultSeverity: "error",
586
- evaluate(context) {
587
- const over = context.events.filter((event) => {
588
- const duration = eventDurationMs(event);
589
- return duration !== void 0 && duration > options.maxDurationMs;
590
- });
591
- if (over.length === 0) return [];
592
- return [
593
- failFinding(
594
- "run.maxStepDuration",
595
- `${over.length} step(s) exceeded max duration ${options.maxDurationMs}ms.`,
596
- over.map((event) => eventEvidence(event, "durationMs")),
597
- { maxDurationMs: options.maxDurationMs },
598
- over.map((event) => ({
599
- eventId: event.eventId,
600
- name: event.name,
601
- durationMs: eventDurationMs(event)
602
- }))
603
- )
604
- ];
605
- }
606
- };
607
- }
608
- function createStallDetectionRule(options = {}) {
609
- const requireEndedAt = options.requireEndedAt === true;
610
- return {
611
- id: "run.stall",
612
- category: "run",
613
- defaultSeverity: "warning",
614
- evaluate(context) {
615
- const findings = [];
616
- const running = context.events.filter((event) => event.status === "running");
617
- if (running.length > 0) {
618
- findings.push(
619
- failFinding(
620
- "run.stall",
621
- `Found ${running.length} event(s) still running (possible stall).`,
622
- running.map((event) => eventEvidence(event, "status")),
623
- "no running events",
624
- running.length
625
- )
626
- );
627
- }
628
- if (requireEndedAt) {
629
- const incomplete = context.events.filter(
630
- (event) => event.startedAt !== void 0 && event.endedAt === void 0 && event.status !== "running"
631
- );
632
- if (incomplete.length > 0) {
633
- findings.push(
634
- failFinding(
635
- "run.stall",
636
- `Found ${incomplete.length} started event(s) without endedAt.`,
637
- incomplete.map((event) => eventEvidence(event, "endedAt")),
638
- "endedAt for started events",
639
- incomplete.length
640
- )
641
- );
642
- }
643
- }
644
- return findings;
645
- }
646
- };
647
- }
648
- function createRequireCompletedRule() {
649
- return {
650
- id: "run.requireCompleted",
651
- category: "run",
652
- defaultSeverity: "error",
653
- evaluate(context) {
654
- const findings = [];
655
- const runStatus = context.selectedRun?.status;
656
- if (runStatus === "running") {
657
- findings.push(
658
- failFinding(
659
- "run.requireCompleted",
660
- "Run is still running.",
661
- runEvidence(context.selectedRun),
662
- "completed run",
663
- runStatus
664
- )
665
- );
666
- }
667
- const running = context.events.filter((event) => event.status === "running");
668
- if (running.length > 0) {
669
- findings.push(
670
- failFinding(
671
- "run.requireCompleted",
672
- `Run has ${running.length} incomplete running event(s).`,
673
- running.map((event) => eventEvidence(event, "status")),
674
- "no running events",
675
- running.length
676
- )
677
- );
678
- }
679
- return findings;
680
- }
681
- };
682
- }
683
- function createRunEventCountRule(options) {
684
- return {
685
- id: "run.eventCount",
686
- category: "run",
687
- defaultSeverity: "error",
688
- evaluate(context) {
689
- const count = context.events.filter(
690
- (event) => options.kind === void 0 || event.kind === options.kind
691
- ).length;
692
- const findings = [];
693
- if (options.min !== void 0 && count < options.min) {
694
- findings.push(
695
- failFinding(
696
- "run.eventCount",
697
- `Event count ${count} was below minimum ${options.min}.`,
698
- runEvidence(context.selectedRun),
699
- { kind: options.kind, min: options.min },
700
- count
701
- )
702
- );
703
- }
704
- if (options.max !== void 0 && count > options.max) {
705
- findings.push(
706
- failFinding(
707
- "run.eventCount",
708
- `Event count ${count} exceeded maximum ${options.max}.`,
709
- runEvidence(context.selectedRun),
710
- { kind: options.kind, max: options.max },
711
- count
712
- )
713
- );
714
- }
715
- return findings;
716
- }
717
- };
718
- }
719
- function createRunDepthRule(options) {
720
- return {
721
- id: "run.depth",
722
- category: "run",
723
- defaultSeverity: "error",
724
- evaluate(context) {
725
- const nodes = [...context.nodesByEventId.values()];
726
- const maxDepth = nodes.reduce((max, node) => Math.max(max, node.depth), 0);
727
- if (maxDepth <= options.maxDepth) return [];
728
- const deepest = nodes.filter((node) => node.depth === maxDepth);
729
- return [
730
- failFinding(
731
- "run.depth",
732
- `Run depth ${maxDepth} exceeded ${options.maxDepth}.`,
733
- deepest.map((node) => ({
734
- runId: node.event.runId,
735
- eventId: node.event.eventId,
736
- parentId: node.event.parentId,
737
- kind: node.event.kind,
738
- name: node.event.name,
739
- status: node.event.status
740
- })),
741
- { maxDepth: options.maxDepth },
742
- maxDepth
743
- )
744
- ];
745
- }
746
- };
747
- }
748
- function createToolUsageRule(options) {
749
- return {
750
- id: "tool.usage",
751
- category: "tool",
752
- defaultSeverity: "error",
753
- evaluate(context) {
754
- const tools = finishedEvents(context, "TOOL");
755
- const names = tools.map(toolName);
756
- const nameSet = new Set(names);
757
- const findings = [];
758
- for (const required of options.required ?? []) {
759
- if (!nameSet.has(required)) {
760
- findings.push(
761
- failFinding("tool.usage", `Required tool ${required} did not appear.`, runEvidence(context.selectedRun), required, names)
762
- );
763
- }
764
- }
765
- const forbidden = new Set(options.forbidden ?? []);
766
- const allowed = options.allowed ? new Set(options.allowed) : void 0;
767
- for (const event of tools) {
768
- const name = toolName(event);
769
- if (forbidden.has(name)) {
770
- findings.push(
771
- failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence(event)], "tool absent", name)
772
- );
773
- }
774
- if (allowed && !allowed.has(name)) {
775
- findings.push(
776
- failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence(event)], [...allowed].sort(), name)
777
- );
778
- }
779
- }
780
- if (options.minCount !== void 0 && tools.length < options.minCount) {
781
- findings.push(
782
- failFinding("tool.usage", `Tool count ${tools.length} was below minimum ${options.minCount}.`, runEvidence(context.selectedRun), { minCount: options.minCount }, tools.length)
783
- );
784
- }
785
- if (options.maxCount !== void 0 && tools.length > options.maxCount) {
786
- findings.push(
787
- failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence(event)), { maxCount: options.maxCount }, tools.length)
788
- );
789
- }
790
- return findings;
791
- }
792
- };
793
- }
794
- function createToolOrderingRule(options) {
795
- return {
796
- id: "tool.order",
797
- category: "tool",
798
- defaultSeverity: "error",
799
- evaluate(context) {
800
- const tools = finishedEvents(context, "TOOL");
801
- const index = firstIndexByName(tools);
802
- const beforeIndex = index.get(options.before);
803
- const afterIndex = index.get(options.after);
804
- if (beforeIndex === void 0 || afterIndex === void 0 || beforeIndex < afterIndex) {
805
- return [];
806
- }
807
- return [
808
- failFinding(
809
- "tool.order",
810
- `Tool ${options.before} must appear before ${options.after}.`,
811
- [eventEvidence(tools[beforeIndex]), eventEvidence(tools[afterIndex])],
812
- { before: options.before, after: options.after },
813
- tools.map(toolName)
814
- )
815
- ];
816
- }
817
- };
818
- }
819
- function createToolFailureRule(options) {
820
- return {
821
- id: "tool.failures",
822
- category: "tool",
823
- defaultSeverity: "error",
824
- evaluate(context) {
825
- const tools = finishedEvents(context, "TOOL");
826
- const failures = tools.filter((event) => event.status === "error");
827
- const retries = tools.map((event) => ({ event, count: retryCount(event) })).filter((item) => item.count !== void 0);
828
- const findings = [];
829
- if (options.maxFailures !== void 0 && failures.length > options.maxFailures) {
830
- findings.push(
831
- failFinding(
832
- "tool.failures",
833
- `Tool failure count ${failures.length} exceeded ${options.maxFailures}.`,
834
- failures.map((event) => eventEvidence(event)),
835
- { maxFailures: options.maxFailures },
836
- failures.length
837
- )
838
- );
839
- }
840
- if (options.maxRetries !== void 0) {
841
- const excessiveRetries = retries.filter((item) => item.count > options.maxRetries);
842
- if (excessiveRetries.length > 0) {
843
- findings.push(
844
- failFinding(
845
- "tool.failures",
846
- `Tool retry count exceeded ${options.maxRetries}.`,
847
- excessiveRetries.map((item) => eventEvidence(item.event, "attributes.retryCount")),
848
- { maxRetries: options.maxRetries },
849
- excessiveRetries.map((item) => ({ tool: toolName(item.event), retries: item.count }))
850
- )
851
- );
852
- }
853
- }
854
- return findings;
855
- }
856
- };
857
- }
858
- function createLlmUsageRule(options) {
859
- return {
860
- id: "llm.usage",
861
- category: "llm",
862
- defaultSeverity: "error",
863
- evaluate(context) {
864
- const llms = finishedEvents(context, "LLM");
865
- const findings = [];
866
- const allowedModels = options.allowedModels ? new Set(options.allowedModels) : void 0;
867
- const allowedProviders = options.allowedProviders ? new Set(options.allowedProviders) : void 0;
868
- const finishReasons = options.finishReasons ? new Set(options.finishReasons) : void 0;
869
- if (options.maxCalls !== void 0 && llms.length > options.maxCalls) {
870
- findings.push(
871
- failFinding(
872
- "llm.usage",
873
- `LLM call count ${llms.length} exceeded ${options.maxCalls}.`,
874
- llms.map((event) => eventEvidence(event)),
875
- { maxCalls: options.maxCalls },
876
- llms.length
877
- )
878
- );
879
- }
880
- for (const event of llms) {
881
- const model = llmModel(event);
882
- const provider = llmProvider(event);
883
- const finishReason = llmFinishReason(event);
884
- if (allowedModels && (!model || !allowedModels.has(model))) {
885
- findings.push(
886
- failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
887
- );
888
- }
889
- if (allowedProviders && (!provider || !allowedProviders.has(provider))) {
890
- findings.push(
891
- failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
892
- );
893
- }
894
- if (finishReasons && (!finishReason || !finishReasons.has(finishReason))) {
895
- findings.push(
896
- failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
897
- );
898
- }
899
- }
900
- const tokenTotals = llms.reduce(
901
- (totals, event) => ({
902
- input: totals.input + (event.tokenUsage?.input ?? 0),
903
- output: totals.output + (event.tokenUsage?.output ?? 0),
904
- total: totals.total + (event.tokenUsage?.total ?? 0),
905
- cached: totals.cached + (event.tokenUsage?.cached ?? 0)
906
- }),
907
- { input: 0, output: 0, total: 0, cached: 0 }
908
- );
909
- const tokenLimits = [
910
- ["input", options.maxInputTokens],
911
- ["output", options.maxOutputTokens],
912
- ["total", options.maxTotalTokens],
913
- ["cached", options.maxCachedTokens]
914
- ];
915
- for (const [key, limit] of tokenLimits) {
916
- if (limit !== void 0 && tokenTotals[key] > limit) {
917
- findings.push(
918
- failFinding(
919
- "llm.usage",
920
- `LLM ${key} token count ${tokenTotals[key]} exceeded ${limit}.`,
921
- llms.map((event) => eventEvidence(event, `tokenUsage.${key}`)),
922
- { [`max${key[0].toUpperCase()}${key.slice(1)}Tokens`]: limit },
923
- tokenTotals[key]
924
- )
925
- );
926
- }
927
- }
928
- return findings;
929
- }
930
- };
931
- }
932
- function createStructureIncompleteRule(options = {}) {
933
- return {
934
- id: "structure.incomplete",
935
- category: "structure",
936
- defaultSeverity: "error",
937
- evaluate(context) {
938
- const findings = [];
939
- if (!options.allowRunning) {
940
- const running = context.events.filter((event) => event.status === "running");
941
- if (running.length > 0) {
942
- findings.push(
943
- failFinding(
944
- "structure.incomplete",
945
- "Trace contains incomplete running events.",
946
- running.map((event) => eventEvidence(event, "status")),
947
- "no running events",
948
- running.length
949
- )
950
- );
951
- }
952
- }
953
- if (options.requireEndedAtForStarted) {
954
- const missingEndedAt = context.events.filter(
955
- (event) => event.startedAt !== void 0 && event.endedAt === void 0 && event.status !== "running"
956
- );
957
- if (missingEndedAt.length > 0) {
958
- findings.push(
959
- failFinding(
960
- "structure.incomplete",
961
- "Trace contains events with startedAt but no endedAt.",
962
- missingEndedAt.map((event) => eventEvidence(event, "endedAt")),
963
- "endedAt for started events",
964
- missingEndedAt.length
965
- )
966
- );
967
- }
968
- }
969
- return findings;
970
- }
971
- };
972
- }
973
- function createStructureOrphanRule(options = {}) {
974
- const allowMarkedUnresolved = options.allowMarkedUnresolved ?? true;
975
- return {
976
- id: "structure.orphan",
977
- category: "structure",
978
- defaultSeverity: "error",
979
- evaluate(context) {
980
- const byId = eventMap(context.events);
981
- const orphans = context.events.filter((event) => {
982
- if (!event.parentId || byId.has(event.parentId)) return false;
983
- return !(allowMarkedUnresolved && parentMarkedUnresolved(event));
984
- });
985
- if (orphans.length === 0) return [];
986
- return [
987
- failFinding(
988
- "structure.orphan",
989
- "Trace contains events whose parentId is not present in the selected run.",
990
- orphans.map((event) => eventEvidence(event, "parentId")),
991
- "parentId resolves to an event in the selected run",
992
- orphans.length
993
- )
994
- ];
995
- }
996
- };
997
- }
998
- function createStructureCycleRule() {
999
- return {
1000
- id: "structure.cycle",
1001
- category: "structure",
1002
- defaultSeverity: "error",
1003
- evaluate(context) {
1004
- const byId = eventMap(context.events);
1005
- const seenCycles = /* @__PURE__ */ new Set();
1006
- const findings = [];
1007
- for (const event of [...context.events].sort((a, b) => a.eventId.localeCompare(b.eventId))) {
1008
- const path = [];
1009
- const seenAt = /* @__PURE__ */ new Map();
1010
- let current = event;
1011
- while (current) {
1012
- const existing = seenAt.get(current.eventId);
1013
- if (existing !== void 0) {
1014
- const cycle = path.slice(existing);
1015
- const key = cycle.map((item) => item.eventId).sort().join("\0");
1016
- if (!seenCycles.has(key)) {
1017
- seenCycles.add(key);
1018
- findings.push(
1019
- failFinding(
1020
- "structure.cycle",
1021
- "Trace contains a parentId cycle.",
1022
- cycle.map((item) => eventEvidence(item, "parentId")),
1023
- "acyclic parentId graph",
1024
- cycle.map((item) => item.eventId).sort()
1025
- )
1026
- );
1027
- }
1028
- break;
1029
- }
1030
- seenAt.set(current.eventId, path.length);
1031
- path.push(current);
1032
- current = current.parentId ? byId.get(current.parentId) : void 0;
1033
- }
1034
- }
1035
- return findings;
1036
- }
1037
- };
1038
- }
1039
- function createStructureRelationshipRule(options = {}) {
1040
- return {
1041
- id: "structure.relationship",
1042
- category: "structure",
1043
- defaultSeverity: "error",
1044
- evaluate(context) {
1045
- const byId = eventMap(context.events);
1046
- const findings = [];
1047
- const minConfidence = options.minConfidence;
1048
- for (const event of context.events) {
1049
- if (minConfidence && CONFIDENCE_RANK[event.confidence] < CONFIDENCE_RANK[minConfidence]) {
1050
- findings.push(
1051
- failFinding(
1052
- "structure.relationship",
1053
- `Event confidence ${event.confidence} is below ${minConfidence}.`,
1054
- [eventEvidence(event, "confidence")],
1055
- { minConfidence },
1056
- event.confidence
1057
- )
1058
- );
1059
- }
1060
- if (!event.parentId) continue;
1061
- if (event.parentId === event.eventId) {
1062
- findings.push(
1063
- failFinding(
1064
- "structure.relationship",
1065
- "Event parentId points to itself.",
1066
- [eventEvidence(event, "parentId")],
1067
- "parentId references a distinct event",
1068
- "self"
1069
- )
1070
- );
1071
- continue;
1072
- }
1073
- const parent = byId.get(event.parentId);
1074
- if (!parent) continue;
1075
- if (options.requireParentBeforeChild) {
1076
- const parentTime = eventStartMs(parent);
1077
- const childTime = eventStartMs(event);
1078
- if (parentTime !== void 0 && childTime !== void 0 && parentTime > childTime) {
1079
- findings.push(
1080
- failFinding(
1081
- "structure.relationship",
1082
- "Parent event starts after child event.",
1083
- [eventEvidence(parent), eventEvidence(event, "parentId")],
1084
- "parent start <= child start",
1085
- { parentEventId: parent.eventId, childEventId: event.eventId }
1086
- )
1087
- );
1088
- }
1089
- }
1090
- if (options.requireTraceParentSpan && parent.trace?.spanId && event.trace) {
1091
- const actual = event.trace.parentSpanId;
1092
- if (actual !== parent.trace.spanId) {
1093
- findings.push(
1094
- failFinding(
1095
- "structure.relationship",
1096
- "Trace parentSpanId does not match parent spanId.",
1097
- [eventEvidence(event, "trace.parentSpanId")],
1098
- { parentSpanId: parent.trace.spanId },
1099
- actual ?? "missing"
1100
- )
1101
- );
1102
- }
1103
- }
1104
- }
1105
- return findings;
1106
- }
1107
- };
1108
- }
1109
- function createStructureParallelWidthRule(options) {
1110
- return {
1111
- id: "structure.parallelWidth",
1112
- category: "structure",
1113
- defaultSeverity: "error",
1114
- evaluate(context) {
1115
- const findings = [];
1116
- const byId = eventMap(context.events);
1117
- if (options.maxChildren !== void 0) {
1118
- for (const [parentId, children] of context.childrenByParentId.entries()) {
1119
- if (children.length <= options.maxChildren) continue;
1120
- const parent = byId.get(parentId);
1121
- findings.push(
1122
- failFinding(
1123
- "structure.parallelWidth",
1124
- `Parent ${parentId} has ${children.length} children, exceeding ${options.maxChildren}.`,
1125
- [
1126
- ...parent ? [eventEvidence(parent)] : [{ runId: context.selectedRun?.runId, eventId: parentId }],
1127
- ...children.map((child) => ({
1128
- runId: child.event.runId,
1129
- eventId: child.event.eventId,
1130
- parentId: child.event.parentId,
1131
- kind: child.event.kind,
1132
- name: child.event.name,
1133
- status: child.event.status
1134
- }))
1135
- ],
1136
- { maxChildren: options.maxChildren },
1137
- children.length
1138
- )
1139
- );
1140
- }
1141
- }
1142
- if (options.maxConcurrent !== void 0) {
1143
- const intervals = context.events.map((event) => ({ event, start: eventStartMs(event), end: eventEndMs(event) })).filter(
1144
- (item) => item.start !== void 0 && item.end !== void 0 && item.end > item.start
1145
- );
1146
- const points = intervals.flatMap((item) => [
1147
- { time: item.start, delta: 1, event: item.event },
1148
- { time: item.end, delta: -1, event: item.event }
1149
- ]);
1150
- points.sort((a, b) => {
1151
- const byTime = a.time - b.time;
1152
- if (byTime !== 0) return byTime;
1153
- const byDelta = a.delta - b.delta;
1154
- if (byDelta !== 0) return byDelta;
1155
- return a.event.eventId.localeCompare(b.event.eventId);
1156
- });
1157
- const active = /* @__PURE__ */ new Map();
1158
- let maxActive = [];
1159
- for (const point of points) {
1160
- if (point.delta > 0) {
1161
- active.set(point.event.eventId, point.event);
1162
- if (active.size > maxActive.length) {
1163
- maxActive = [...active.values()].sort((a, b) => a.eventId.localeCompare(b.eventId));
1164
- }
1165
- } else {
1166
- active.delete(point.event.eventId);
1167
- }
1168
- }
1169
- if (maxActive.length > options.maxConcurrent) {
1170
- findings.push(
1171
- failFinding(
1172
- "structure.parallelWidth",
1173
- `Concurrent event width ${maxActive.length} exceeded ${options.maxConcurrent}.`,
1174
- maxActive.map((event) => eventEvidence(event)),
1175
- { maxConcurrent: options.maxConcurrent },
1176
- maxActive.length
1177
- )
1178
- );
1179
- }
1180
- }
1181
- return findings;
1182
- }
1183
- };
1184
- }
1185
- function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
1186
- return {
1187
- id: ruleId,
1188
- category: "structure",
1189
- defaultSeverity: "error",
1190
- evaluate(context) {
1191
- const events = selectEvents(context);
1192
- const names = events.map(nameForEvent);
1193
- const nameSet = new Set(names);
1194
- const findings = [];
1195
- for (const required of sortedStrings(options.required)) {
1196
- if (!nameSet.has(required)) {
1197
- findings.push(
1198
- failFinding(
1199
- ruleId,
1200
- `Required ${label} ${required} did not appear.`,
1201
- runEvidence(context.selectedRun),
1202
- required,
1203
- names
1204
- )
1205
- );
1206
- }
1207
- }
1208
- const forbidden = new Set(options.forbidden ?? []);
1209
- const allowed = options.allowed ? new Set(options.allowed) : void 0;
1210
- for (const event of events) {
1211
- const name = nameForEvent(event);
1212
- if (forbidden.has(name)) {
1213
- findings.push(
1214
- failFinding(
1215
- ruleId,
1216
- `Forbidden ${label} ${name} appeared.`,
1217
- [eventEvidence(event)],
1218
- `${label} absent`,
1219
- name
1220
- )
1221
- );
1222
- }
1223
- if (allowed && !allowed.has(name)) {
1224
- findings.push(
1225
- failFinding(
1226
- ruleId,
1227
- `${label[0].toUpperCase()}${label.slice(1)} ${name} is not in the allowed set.`,
1228
- [eventEvidence(event)],
1229
- [...allowed].sort(),
1230
- name
1231
- )
1232
- );
1233
- }
1234
- }
1235
- if (options.minCount !== void 0 && events.length < options.minCount) {
1236
- findings.push(
1237
- failFinding(
1238
- ruleId,
1239
- `${label[0].toUpperCase()}${label.slice(1)} count ${events.length} was below minimum ${options.minCount}.`,
1240
- runEvidence(context.selectedRun),
1241
- { minCount: options.minCount },
1242
- events.length
1243
- )
1244
- );
1245
- }
1246
- if (options.maxCount !== void 0 && events.length > options.maxCount) {
1247
- findings.push(
1248
- failFinding(
1249
- ruleId,
1250
- `${label[0].toUpperCase()}${label.slice(1)} count ${events.length} exceeded maximum ${options.maxCount}.`,
1251
- events.map((event) => eventEvidence(event)),
1252
- { maxCount: options.maxCount },
1253
- events.length
1254
- )
1255
- );
1256
- }
1257
- return findings;
1258
- }
1259
- };
1260
- }
1261
- function createRetrievalRule(options) {
1262
- return createSignalRule(
1263
- "structure.retrieval",
1264
- "retrieval",
1265
- options,
1266
- (context) => finishedEvents(context, "RETRIEVER"),
1267
- (event) => signalName(event, ["retrievalName", "retrieverName", "retriever"], ["retriever:", "retrieval:"])
1268
- );
1269
- }
1270
- function createGuardrailRule(options) {
1271
- return createSignalRule(
1272
- "structure.guardrail",
1273
- "guardrail",
1274
- options,
1275
- guardrailEvents,
1276
- (event) => signalName(event, ["guardrailName", "guardrail", "guardrailId"], ["guardrail:"])
1277
- );
1278
- }
1279
- function createDecisionRule(options) {
1280
- return createSignalRule(
1281
- "structure.decision",
1282
- "decision",
1283
- options,
1284
- (context) => finishedEvents(context, "DECISION"),
1285
- (event) => signalName(event, ["decisionName", "decision", "decisionId"], ["decision:"])
1286
- );
1287
- }
1288
- function createSafetyRedactionRule(options = {}) {
1289
- const sensitiveKeys = options.sensitiveKeys ?? DEFAULT_SENSITIVE_KEYS;
1290
- const markers = options.redactedMarkers ?? ["[REDACTED]", "[REDACTED:"];
1291
- return {
1292
- id: "safety.redaction",
1293
- category: "safety",
1294
- defaultSeverity: "error",
1295
- evaluate(context) {
1296
- const findings = [];
1297
- for (const event of context.events) {
1298
- for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
1299
- if (!isSensitiveKey(entry.key ?? lastPathSegment(entry.path), sensitiveKeys)) continue;
1300
- if (typeof entry.value === "string" && hasRedactionMarker(entry.value, markers)) continue;
1301
- findings.push(
1302
- failFinding(
1303
- "safety.redaction",
1304
- `Sensitive-looking field at ${entry.path} is not redacted.`,
1305
- [eventEvidence(event, entry.path)],
1306
- "redaction marker",
1307
- { path: entry.path, valueType: valueType(entry.value) }
1308
- )
1309
- );
1310
- }
1311
- }
1312
- return limitFindings(findings, options.maxFindings);
1313
- }
1314
- };
1315
- }
1316
- function createSafetyRawContentRule(options = {}) {
1317
- const forbiddenKeys = options.forbiddenKeys ?? DEFAULT_RAW_CONTENT_KEYS;
1318
- return {
1319
- id: "safety.rawPrompt",
1320
- category: "safety",
1321
- defaultSeverity: "error",
1322
- evaluate(context) {
1323
- const findings = [];
1324
- for (const event of context.events) {
1325
- for (const entry of eventValueEntries(event, { includeSummaries: options.includeSummaries })) {
1326
- const key = entry.key ?? lastPathSegment(entry.path);
1327
- if (!isRawContentKey(key, forbiddenKeys)) continue;
1328
- findings.push(
1329
- failFinding(
1330
- "safety.rawPrompt",
1331
- `Raw content-like field ${entry.path} is present.`,
1332
- [eventEvidence(event, entry.path)],
1333
- "metadata-only trace fields",
1334
- { path: entry.path, valueType: valueType(entry.value) }
1335
- )
1336
- );
1337
- }
1338
- }
1339
- return limitFindings(findings, options.maxFindings);
1340
- }
1341
- };
1342
- }
1343
- function createSafetySecretPatternRule(options = {}) {
1344
- const patterns = options.patterns ?? DEFAULT_SECRET_PATTERNS;
1345
- const maxStringLength = options.maxStringLength ?? 4096;
1346
- return {
1347
- id: "safety.secretPattern",
1348
- category: "safety",
1349
- defaultSeverity: "error",
1350
- evaluate(context) {
1351
- const findings = [];
1352
- for (const event of context.events) {
1353
- for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
1354
- if (typeof entry.value !== "string") continue;
1355
- const sample = entry.value.slice(0, maxStringLength);
1356
- for (const pattern of patterns) {
1357
- pattern.pattern.lastIndex = 0;
1358
- if (!pattern.pattern.test(sample)) continue;
1359
- pattern.pattern.lastIndex = 0;
1360
- findings.push(
1361
- failFinding(
1362
- "safety.secretPattern",
1363
- `Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
1364
- [eventEvidence(event, entry.path)],
1365
- "no secret-like strings",
1366
- { pattern: pattern.id, path: entry.path }
1367
- )
1368
- );
1369
- break;
1370
- }
1371
- }
1372
- }
1373
- return limitFindings(findings, options.maxFindings);
1374
- }
1375
- };
1376
- }
1377
- function createSafetyOversizedAttributeRule(options) {
1378
- return {
1379
- id: "safety.oversizedAttribute",
1380
- category: "safety",
1381
- defaultSeverity: "error",
1382
- evaluate(context) {
1383
- const findings = [];
1384
- for (const event of context.events) {
1385
- for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
1386
- if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
1387
- findings.push(
1388
- failFinding(
1389
- "safety.oversizedAttribute",
1390
- `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
1391
- [eventEvidence(event, entry.path)],
1392
- { maxStringLength: options.maxStringLength },
1393
- { path: entry.path, length: entry.value.length }
1394
- )
1395
- );
1396
- }
1397
- if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
1398
- findings.push(
1399
- failFinding(
1400
- "safety.oversizedAttribute",
1401
- `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
1402
- [eventEvidence(event, entry.path)],
1403
- { maxArrayLength: options.maxArrayLength },
1404
- { path: entry.path, length: entry.value.length }
1405
- )
1406
- );
1407
- }
1408
- if (isRecord(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
1409
- findings.push(
1410
- failFinding(
1411
- "safety.oversizedAttribute",
1412
- `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
1413
- [eventEvidence(event, entry.path)],
1414
- { maxObjectKeys: options.maxObjectKeys },
1415
- { path: entry.path, keys: Object.keys(entry.value).length }
1416
- )
1417
- );
1418
- }
1419
- if (options.maxSerializedBytes !== void 0) {
1420
- const bytes = serializedByteLength(entry.value);
1421
- if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
1422
- findings.push(
1423
- failFinding(
1424
- "safety.oversizedAttribute",
1425
- `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
1426
- [eventEvidence(event, entry.path)],
1427
- { maxSerializedBytes: options.maxSerializedBytes },
1428
- { path: entry.path, bytes }
1429
- )
1430
- );
1431
- }
1432
- }
1433
- }
1434
- }
1435
- return limitFindings(findings, options.maxFindings);
1436
- }
1437
- };
1438
- }
1439
- function createBaselineRegressionRule(options) {
1440
- return {
1441
- id: "baseline.regression",
1442
- category: "baseline",
1443
- defaultSeverity: "error",
1444
- evaluate(context) {
1445
- const baselineSelection = resolveSelectedRun(options.baseline, options.baselineRunId);
1446
- if (baselineSelection.diagnostics.length > 0 || !baselineSelection.run) {
1447
- return [
1448
- failFinding(
1449
- "baseline.regression",
1450
- "Baseline run could not be selected.",
1451
- runEvidence(context.selectedRun),
1452
- "selectable baseline run",
1453
- baselineSelection.diagnostics.map((item) => item.code)
1454
- )
1455
- ];
1456
- }
1457
- const baselineFacts = buildFacts(options.baseline, baselineSelection.run);
1458
- const baselineContext = {
1459
- ...baselineFacts,
1460
- selectedRun: baselineSelection.run,
1461
- sourceLabel: options.baseline.sourceLabel
1462
- };
1463
- const findings = [];
1464
- const durationToleranceMs = options.durationToleranceMs ?? 0;
1465
- if (options.compareFormat && baselineContext.format !== context.format) {
1466
- findings.push(
1467
- baselineDiffFinding(
1468
- "Trace format differs from baseline.",
1469
- runEvidence(context.selectedRun),
1470
- baselineContext.format,
1471
- context.format
1472
- )
1473
- );
1474
- }
1475
- const baselineRunStatus = baselineContext.selectedRun?.status ?? "unknown";
1476
- const candidateRunStatus = context.selectedRun?.status ?? "unknown";
1477
- if (baselineRunStatus !== candidateRunStatus) {
1478
- findings.push(
1479
- baselineDiffFinding(
1480
- "Run status differs from baseline.",
1481
- runEvidence(context.selectedRun),
1482
- baselineRunStatus,
1483
- candidateRunStatus
1484
- )
1485
- );
1486
- }
1487
- const baselineDuration = baselineContext.selectedRun?.durationMs;
1488
- const candidateDuration = context.selectedRun?.durationMs;
1489
- if (baselineDuration !== void 0 && candidateDuration !== void 0 && Math.abs(candidateDuration - baselineDuration) > durationToleranceMs) {
1490
- findings.push(
1491
- baselineDiffFinding(
1492
- "Run duration differs from baseline beyond tolerance.",
1493
- runEvidence(context.selectedRun),
1494
- { durationMs: baselineDuration, toleranceMs: durationToleranceMs },
1495
- candidateDuration
1496
- )
1497
- );
1498
- }
1499
- const comparisons = [
1500
- {
1501
- label: "Tree shape",
1502
- path: "tree",
1503
- expected: treeShape(baselineContext.rootNodes),
1504
- actual: treeShape(context.rootNodes),
1505
- evidence: runEvidence(context.selectedRun)
1506
- },
1507
- {
1508
- label: "Event statuses",
1509
- path: "status",
1510
- expected: statusShape(baselineContext),
1511
- actual: statusShape(context),
1512
- evidence: runEvidence(context.selectedRun)
1513
- },
1514
- {
1515
- label: "Tool usage",
1516
- path: "tool",
1517
- expected: toolShape(baselineContext),
1518
- actual: toolShape(context),
1519
- evidence: firstEvidenceForKind(context, "TOOL", "tool")
1520
- },
1521
- {
1522
- label: "LLM usage",
1523
- path: "llm",
1524
- expected: llmShape(baselineContext),
1525
- actual: llmShape(context),
1526
- evidence: firstEvidenceForKind(context, "LLM", "llm")
1527
- },
1528
- {
1529
- label: "Error profile",
1530
- path: "error",
1531
- expected: errorShape(baselineContext),
1532
- actual: errorShape(context),
1533
- evidence: firstEvidenceForKind(context, "ERROR", "error")
1534
- },
1535
- {
1536
- label: "Retrieval signals",
1537
- path: "retrieval",
1538
- expected: retrievalShape(baselineContext),
1539
- actual: retrievalShape(context),
1540
- evidence: firstEvidenceForKind(context, "RETRIEVER", "retrieval")
1541
- },
1542
- {
1543
- label: "Guardrail signals",
1544
- path: "guardrail",
1545
- expected: guardrailShape(baselineContext),
1546
- actual: guardrailShape(context),
1547
- evidence: guardrailEvents(context)[0] ? [eventEvidence(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
1548
- }
1549
- ];
1550
- for (const comparison of comparisons) {
1551
- if (JSON.stringify(comparison.expected) === JSON.stringify(comparison.actual)) {
1552
- continue;
1553
- }
1554
- findings.push(
1555
- baselineDiffFinding(
1556
- `${comparison.label} differs from baseline.`,
1557
- comparison.evidence.length > 0 ? comparison.evidence : [{ runId: context.selectedRun?.runId, path: comparison.path }],
1558
- comparison.expected,
1559
- comparison.actual
1560
- )
1561
- );
1562
- }
1563
- return findings;
1564
- }
1565
- };
1566
- }
1567
- function runTraceChecks(input, options = {}) {
1568
- const selected = resolveSelectedRun(input, options.runId);
1569
- if (selected.diagnostics.length > 0) {
1570
- return errorResult(input, selected.diagnostics, selected.run);
1571
- }
1572
- const rules = selectRules(options.rules ?? [], options.select);
1573
- if (rules.diagnostics.length > 0) {
1574
- return errorResult(input, rules.diagnostics, selected.run);
1575
- }
1576
- const facts = buildFacts(input, selected.run);
1577
- const context = {
1578
- ...facts,
1579
- ...selected.run ? { selectedRun: selected.run } : {},
1580
- ...input.sourceLabel ? { sourceLabel: input.sourceLabel } : {}
1581
- };
1582
- const diagnostics = [];
1583
- const findings = [];
1584
- for (const rule of rules.rules) {
1585
- try {
1586
- findings.push(...rule.evaluate(context).map((finding) => normalizeFinding(rule, finding)));
1587
- } catch (error) {
1588
- const message = error instanceof Error ? error.message : String(error);
1589
- diagnostics.push(
1590
- diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
1591
- );
1592
- }
1593
- }
1594
- if (diagnostics.length > 0) {
1595
- return errorResult(input, diagnostics, selected.run);
1596
- }
1597
- const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
1598
- const sortedFindings = findings.sort(compareFindings(eventById));
1599
- const summary = summarize(sortedFindings, diagnostics);
1600
- const status = summary.failed > 0 ? "fail" : "pass";
1601
- return {
1602
- ok: status === "pass",
1603
- status,
1604
- format: input.read.format,
1605
- ...selected.run ? { runId: selected.run.runId } : {},
1606
- summary,
1607
- findings: sortedFindings,
1608
- diagnostics
1609
- };
1610
- }
1611
-
1612
- export { createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createMaxStepDurationRule, createRequireCompletedRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, runTraceChecks };
1
+ export { createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, runTraceChecks } from './chunk-QLYOCCEF.mjs';
2
+ import './chunk-VFO76UH3.mjs';
3
+ import './chunk-ULCGIRTC.mjs';
4
+ import './chunk-VU6O5QAH.mjs';
5
+ import './chunk-WRE4CNPF.mjs';
6
+ import './chunk-P366CCL2.mjs';
7
+ import './chunk-7TGZLWEE.mjs';
1613
8
  //# sourceMappingURL=checks.mjs.map
1614
9
  //# sourceMappingURL=checks.mjs.map