@agent-inspect/mcp-server 6.27.0 → 6.29.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.
package/dist/index.cjs CHANGED
@@ -351,6 +351,8 @@ function isPersistedTokenUsage(value) {
351
351
  if (!isOptionalNonNegativeNumber(value.output)) return false;
352
352
  if (!isOptionalNonNegativeNumber(value.total)) return false;
353
353
  if (!isOptionalNonNegativeNumber(value.cached)) return false;
354
+ if (!isOptionalNonNegativeNumber(value.cacheWrite)) return false;
355
+ if (!isOptionalNonNegativeNumber(value.reasoning)) return false;
354
356
  return true;
355
357
  }
356
358
  function isPersistedTraceContext(value) {
@@ -1253,7 +1255,13 @@ function buildRunSummary(events) {
1253
1255
  tokensInput: isNonNegativeFiniteNumber(s.metadata?.tokens?.input) ? s.metadata.tokens.input : void 0,
1254
1256
  tokensOutput: isNonNegativeFiniteNumber(s.metadata?.tokens?.output) ? s.metadata.tokens.output : void 0,
1255
1257
  tokensTotal: isNonNegativeFiniteNumber(s.metadata?.tokens?.total) ? s.metadata.tokens.total : void 0,
1256
- tokensCached: isNonNegativeFiniteNumber(s.metadata?.tokens?.cached) ? s.metadata.tokens.cached : void 0
1258
+ tokensCached: isNonNegativeFiniteNumber(s.metadata?.tokens?.cached) ? s.metadata.tokens.cached : void 0,
1259
+ tokensCacheWrite: isNonNegativeFiniteNumber(
1260
+ s.metadata?.tokens?.cacheWrite
1261
+ ) ? s.metadata.tokens.cacheWrite : void 0,
1262
+ tokensReasoning: isNonNegativeFiniteNumber(
1263
+ s.metadata?.tokens?.reasoning
1264
+ ) ? s.metadata.tokens.reasoning : void 0
1257
1265
  });
1258
1266
  }
1259
1267
  }
@@ -1277,9 +1285,13 @@ function buildRunSummary(events) {
1277
1285
  let totalTokensOutput = 0;
1278
1286
  let totalTokensTotal = 0;
1279
1287
  let totalTokensCached = 0;
1288
+ let totalTokensCacheWrite = 0;
1289
+ let totalTokensReasoning = 0;
1280
1290
  let tokenBearingSteps = 0;
1281
1291
  let stepsWithKnownTotal = 0;
1282
1292
  let hasCachedTokens = false;
1293
+ let hasCacheWriteTokens = false;
1294
+ let hasReasoningTokens = false;
1283
1295
  const depthCache = /* @__PURE__ */ new Map();
1284
1296
  for (const [id, s] of steps.entries()) {
1285
1297
  totalSteps += 1;
@@ -1294,7 +1306,7 @@ function buildRunSummary(events) {
1294
1306
  longestStep = { name: s.name, durationMs: s.durationMs, type: s.type };
1295
1307
  }
1296
1308
  }
1297
- if (s.tokensInput !== void 0 || s.tokensOutput !== void 0 || s.tokensTotal !== void 0 || s.tokensCached !== void 0) {
1309
+ if (s.tokensInput !== void 0 || s.tokensOutput !== void 0 || s.tokensTotal !== void 0 || s.tokensCached !== void 0 || s.tokensCacheWrite !== void 0 || s.tokensReasoning !== void 0) {
1298
1310
  tokenBearingSteps += 1;
1299
1311
  if (s.tokensInput !== void 0) totalTokensInput += s.tokensInput;
1300
1312
  if (s.tokensOutput !== void 0) totalTokensOutput += s.tokensOutput;
@@ -1309,6 +1321,14 @@ function buildRunSummary(events) {
1309
1321
  totalTokensCached += s.tokensCached;
1310
1322
  hasCachedTokens = true;
1311
1323
  }
1324
+ if (s.tokensCacheWrite !== void 0) {
1325
+ totalTokensCacheWrite += s.tokensCacheWrite;
1326
+ hasCacheWriteTokens = true;
1327
+ }
1328
+ if (s.tokensReasoning !== void 0) {
1329
+ totalTokensReasoning += s.tokensReasoning;
1330
+ hasReasoningTokens = true;
1331
+ }
1312
1332
  }
1313
1333
  }
1314
1334
  const summary = {
@@ -1328,7 +1348,9 @@ function buildRunSummary(events) {
1328
1348
  input: totalTokensInput,
1329
1349
  output: totalTokensOutput,
1330
1350
  ...stepsWithKnownTotal === tokenBearingSteps ? { total: totalTokensTotal } : {},
1331
- ...hasCachedTokens ? { cached: totalTokensCached } : {}
1351
+ ...hasCachedTokens ? { cached: totalTokensCached } : {},
1352
+ ...hasCacheWriteTokens ? { cacheWrite: totalTokensCacheWrite } : {},
1353
+ ...hasReasoningTokens ? { reasoning: totalTokensReasoning } : {}
1332
1354
  }
1333
1355
  } : {}
1334
1356
  };
@@ -1599,6 +1621,12 @@ function renderRunWhat(summary, options = {}) {
1599
1621
  if (summary.totalTokens.cached !== void 0) {
1600
1622
  tokenParts.push(`${summary.totalTokens.cached} cached`);
1601
1623
  }
1624
+ if (summary.totalTokens.cacheWrite !== void 0) {
1625
+ tokenParts.push(`${summary.totalTokens.cacheWrite} cache-write`);
1626
+ }
1627
+ if (summary.totalTokens.reasoning !== void 0) {
1628
+ tokenParts.push(`${summary.totalTokens.reasoning} reasoning`);
1629
+ }
1602
1630
  lines.push(`Tokens: ${tokenParts.join(" / ")}`);
1603
1631
  }
1604
1632
  if (showCorrelation && summary.correlation) {
@@ -2150,6 +2178,9 @@ function inferEvidenceFileRole(relativePath) {
2150
2178
  if (base === "check-results.json") {
2151
2179
  return "checks";
2152
2180
  }
2181
+ if (base === "contract.resolved.json") {
2182
+ return "contract";
2183
+ }
2153
2184
  if (base === "redaction-report.json") {
2154
2185
  return "redaction-report";
2155
2186
  }
@@ -2216,1921 +2247,1461 @@ function buildEvidenceManifest(parts) {
2216
2247
  },
2217
2248
  assessment,
2218
2249
  ...parts.semantics !== void 0 ? { semantics: { ...parts.semantics } } : {},
2250
+ ...parts.contract !== void 0 ? { contract: { ...parts.contract } } : {},
2219
2251
  files: buildEvidenceFileEntries(parts.files)
2220
2252
  };
2221
2253
  }
2222
2254
 
2223
- // packages/core/src/exporters/helpers.ts
2224
- var REDACT_SUBSTRINGS = [
2255
+ // packages/core/src/diagnostics/programmatic.ts
2256
+ var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
2257
+ AI_TRACE_INPUT_INVALID: {
2258
+ code: "AI_TRACE_INPUT_INVALID",
2259
+ summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
2260
+ remediation: "For a file path, use openTraceFile(path).",
2261
+ relatedCodes: ["invalid_input"]
2262
+ },
2263
+ AI_TRACE_FORMAT_UNSUPPORTED: {
2264
+ code: "AI_TRACE_FORMAT_UNSUPPORTED",
2265
+ summary: "No trace reader could detect the input format.",
2266
+ remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
2267
+ relatedCodes: ["unsupported_format"]
2268
+ },
2269
+ AI_TRACE_FORMAT_AMBIGUOUS: {
2270
+ code: "AI_TRACE_FORMAT_AMBIGUOUS",
2271
+ summary: "Multiple trace readers matched the input with equal confidence.",
2272
+ remediation: "Set options.format explicitly to disambiguate the reader.",
2273
+ relatedCodes: ["ambiguous_format"]
2274
+ },
2275
+ AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
2276
+ code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
2277
+ summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
2278
+ remediation: "Use openTraceFile() to normalize a JSONL trace first."
2279
+ },
2280
+ AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
2281
+ code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
2282
+ summary: "Multiple runs are available; select a run before executing checks.",
2283
+ remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
2284
+ relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
2285
+ },
2286
+ AI_TRACE_RELATIONSHIP_SELF_PARENT: {
2287
+ code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2288
+ summary: "A parentId equals its own event/step id.",
2289
+ remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
2290
+ relatedCodes: [
2291
+ "AI_LANGGRAPH_SELF_PARENT_REJECTED",
2292
+ "AI_LOGICAL_SELF_PARENT_REMOVED"
2293
+ ]
2294
+ },
2295
+ AI_TRACE_RELATIONSHIP_CYCLE: {
2296
+ code: "AI_TRACE_RELATIONSHIP_CYCLE",
2297
+ summary: "Trace contains a parentId cycle.",
2298
+ remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
2299
+ relatedCodes: ["structure.cycle"]
2300
+ }
2301
+ });
2302
+ function formatProgrammaticDiagnostic(code, detail) {
2303
+ const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
2304
+ const summary = detail?.trim() ? detail.trim() : spec.summary;
2305
+ return `${code}: ${summary} Remediation: ${spec.remediation}`;
2306
+ }
2307
+
2308
+ // packages/core/src/safety/sensitive-key.ts
2309
+ function keyHasExplicitSeparator(value) {
2310
+ return /[_\-.]/.test(value);
2311
+ }
2312
+ function normalizeSensitiveKey(value) {
2313
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2314
+ }
2315
+ var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
2316
+ [
2317
+ "tokens",
2318
+ "max_tokens",
2319
+ "min_tokens",
2320
+ "ls_max_tokens",
2321
+ "token_count",
2322
+ "token_limit",
2323
+ "token_budget",
2324
+ "input_tokens",
2325
+ "output_tokens",
2326
+ "total_tokens",
2327
+ "cached_tokens",
2328
+ "prompt_tokens",
2329
+ "completion_tokens"
2330
+ ].map(normalizeSensitiveKey)
2331
+ );
2332
+ var DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
2225
2333
  "authorization",
2226
2334
  "cookie",
2227
2335
  "token",
2336
+ "access_token",
2337
+ "accesstoken",
2338
+ "auth_token",
2339
+ "authtoken",
2340
+ "refresh_token",
2341
+ "refreshtoken",
2342
+ "id_token",
2343
+ "idtoken",
2344
+ "bearer_token",
2345
+ "bearertoken",
2346
+ "api_token",
2347
+ "apitoken",
2228
2348
  "apikey",
2349
+ "api_key",
2229
2350
  "password",
2230
2351
  "secret",
2231
2352
  "email"
2232
2353
  ];
2233
- function shouldRedactKey(key) {
2234
- const k = key.toLowerCase();
2235
- for (const s of REDACT_SUBSTRINGS) {
2236
- if (k.includes(s)) return true;
2237
- }
2238
- return false;
2354
+ function isTokenCredentialKey(normalized) {
2355
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
2356
+ if (normalized === "token") return true;
2357
+ if (normalized.endsWith("tokens")) return false;
2358
+ return normalized.endsWith("token");
2239
2359
  }
2240
- function safeString(value, maxLength) {
2241
- if (value === null || value === void 0) return "";
2242
- let s;
2243
- if (typeof value === "string") s = value;
2244
- else if (typeof value === "number" || typeof value === "boolean") s = String(value);
2245
- else s = stableJson(value, false);
2246
- if (maxLength !== void 0 && maxLength >= 0 && s.length > maxLength) {
2247
- return `${s.slice(0, maxLength)}\u2026`;
2360
+ function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
2361
+ if (!key) return false;
2362
+ const normalized = normalizeSensitiveKey(key);
2363
+ if (!normalized) return false;
2364
+ if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
2365
+ const allowPrefixCompound = keyHasExplicitSeparator(key);
2366
+ for (const sensitive of sensitiveKeys) {
2367
+ const s = normalizeSensitiveKey(sensitive);
2368
+ if (!s) continue;
2369
+ if (s === "token") {
2370
+ if (isTokenCredentialKey(normalized)) return true;
2371
+ continue;
2372
+ }
2373
+ if (normalized === s) return true;
2374
+ if (normalized.endsWith(`_${s}`)) return true;
2375
+ if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
2248
2376
  }
2249
- return s;
2377
+ return false;
2250
2378
  }
2251
- function escapeMarkdown(value) {
2252
- return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
2379
+
2380
+ // packages/core/src/checks/logical-events.ts
2381
+ function isRecord6(value) {
2382
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2253
2383
  }
2254
- function escapeHtml(value) {
2255
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
2384
+ function legacyEvent(event) {
2385
+ const value = event.attributes?.legacyEvent;
2386
+ return typeof value === "string" ? value : void 0;
2256
2387
  }
2257
- function sortKeysDeep(input) {
2258
- if (input === null || typeof input !== "object") return input;
2259
- if (Array.isArray(input)) return input.map(sortKeysDeep);
2260
- const o = input;
2261
- const out = {};
2262
- for (const k of Object.keys(o).sort()) {
2263
- out[k] = sortKeysDeep(o[k]);
2264
- }
2265
- return out;
2388
+ function stepIdOf(event) {
2389
+ const value = event.attributes?.stepId;
2390
+ if (typeof value === "string" && value.trim() !== "") return value;
2391
+ return void 0;
2266
2392
  }
2267
- function stableJson(value, pretty) {
2268
- const sorted = sortKeysDeep(value);
2269
- return pretty === true ? JSON.stringify(sorted, null, 2) : JSON.stringify(sorted);
2393
+ function cloneEvent(event) {
2394
+ return {
2395
+ ...event,
2396
+ ...event.attributes !== void 0 ? { attributes: { ...event.attributes } } : {},
2397
+ ...event.error !== void 0 ? { error: { ...event.error } } : {},
2398
+ ...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
2399
+ ...event.source !== void 0 ? { source: { ...event.source } } : {}
2400
+ };
2270
2401
  }
2271
- function compactAttributes(attrs2, options) {
2272
- if (attrs2 === void 0) return {};
2273
- const maxLen = options?.maxLength ?? 500;
2274
- const redacted = options?.redacted ?? true;
2275
- const out = {};
2276
- for (const key of Object.keys(attrs2).sort()) {
2277
- if (redacted && shouldRedactKey(key)) {
2278
- out[key] = "[REDACTED]";
2279
- continue;
2280
- }
2281
- const v = attrs2[key];
2282
- out[key] = compactValue(v, maxLen, redacted);
2402
+ function mergeAttributes(start, complete) {
2403
+ const merged = {
2404
+ ...isRecord6(complete.attributes) ? complete.attributes : {},
2405
+ ...isRecord6(start.attributes) ? start.attributes : {}
2406
+ };
2407
+ merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
2408
+ merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
2409
+ if (complete.attributes?.errorStack !== void 0) {
2410
+ merged.errorStack = complete.attributes.errorStack;
2283
2411
  }
2284
- return out;
2412
+ return Object.keys(merged).length > 0 ? merged : void 0;
2285
2413
  }
2286
- function compactValue(value, maxLen, redacted) {
2287
- if (value === null || typeof value !== "object") {
2288
- return typeof value === "string" ? safeString(value, maxLen) : value;
2289
- }
2290
- if (Array.isArray(value)) {
2291
- const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen, redacted));
2292
- if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
2293
- return arr;
2294
- }
2295
- const o = value;
2296
- const inner = {};
2297
- for (const k of Object.keys(o)) {
2298
- if (redacted && shouldRedactKey(k)) inner[k] = "[REDACTED]";
2299
- else inner[k] = compactValue(o[k], maxLen, redacted);
2300
- }
2301
- return inner;
2414
+ function pairStartComplete(start, complete) {
2415
+ const attributes = mergeAttributes(start, complete);
2416
+ const paired = {
2417
+ ...cloneEvent(start),
2418
+ status: complete.status,
2419
+ timestamp: complete.timestamp ?? start.timestamp,
2420
+ ...complete.endedAt !== void 0 ? { endedAt: complete.endedAt } : {},
2421
+ ...complete.durationMs !== void 0 ? { durationMs: complete.durationMs } : {},
2422
+ ...complete.error !== void 0 ? { error: { ...complete.error } } : {},
2423
+ ...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
2424
+ ...attributes !== void 0 ? { attributes } : {}
2425
+ };
2426
+ return {
2427
+ ...paired,
2428
+ sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
2429
+ projection: {
2430
+ paired: true,
2431
+ absorbedEventIds: Object.freeze([complete.eventId]),
2432
+ parentNormalized: false,
2433
+ ...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
2434
+ }
2435
+ };
2302
2436
  }
2303
- function flattenTree(tree) {
2304
- const out = [];
2305
- function walk(nodes) {
2306
- for (const n of nodes) {
2307
- out.push(n);
2308
- if (n.children.length > 0) walk(n.children);
2437
+ function asLogical(event, extras) {
2438
+ return {
2439
+ ...cloneEvent(event),
2440
+ sourceEventIds: Object.freeze([event.eventId]),
2441
+ projection: {
2442
+ paired: false,
2443
+ absorbedEventIds: Object.freeze([]),
2444
+ parentNormalized: extras?.parentNormalized === true,
2445
+ ...{}
2309
2446
  }
2310
- }
2311
- walk(tree.children);
2312
- return out;
2447
+ };
2313
2448
  }
2314
-
2315
- // packages/core/src/diff/comparable.ts
2316
- function extractOutputPreview(meta) {
2317
- if (meta === void 0) return void 0;
2318
- if ("outputPreview" in meta) return meta.outputPreview;
2319
- if ("resultPreview" in meta) return meta.resultPreview;
2320
- return void 0;
2321
- }
2322
- function mapStepStatus(s) {
2323
- if (s === void 0) return "running";
2324
- return s;
2325
- }
2326
- function manualTraceEventsToComparableRun(events) {
2327
- const started = events.find((e) => e.event === "run_started");
2328
- if (!started || started.event !== "run_started") {
2329
- throw new Error("Invalid trace: missing run_started");
2330
- }
2331
- const rs = started;
2332
- const runId = rs.runId;
2333
- const completedAll = events.filter((e) => e.event === "run_completed");
2334
- const lastCompleted = completedAll[completedAll.length - 1];
2335
- let runStatus;
2336
- if (lastCompleted === void 0) runStatus = "running";
2337
- else runStatus = lastCompleted.status;
2338
- const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
2339
- const steps = /* @__PURE__ */ new Map();
2340
- let order = 0;
2341
- for (const e of events) {
2342
- if (e.event !== "step_started") continue;
2343
- const s = e;
2344
- const meta = s.metadata ? { ...s.metadata } : void 0;
2345
- steps.set(s.stepId, {
2346
- id: s.stepId,
2347
- parentId: s.parentId,
2348
- name: s.name,
2349
- type: s.type,
2350
- order: order++,
2351
- timestamp: s.timestamp,
2352
- metadata: meta
2353
- });
2354
- }
2355
- for (const e of events) {
2356
- if (e.event !== "step_completed") continue;
2357
- const acc = steps.get(e.stepId);
2358
- if (!acc) continue;
2359
- acc.status = e.status;
2360
- acc.durationMs = e.durationMs;
2361
- if (e.error?.message) acc.errorMsg = e.error.message;
2362
- const extra = e;
2363
- if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
2364
- acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
2365
- }
2366
- }
2367
- const nodes = /* @__PURE__ */ new Map();
2368
- for (const acc of steps.values()) {
2369
- let meta = acc.metadata ? { ...acc.metadata } : void 0;
2370
- if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
2371
- meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
2372
- }
2373
- const outputPreview = extractOutputPreview(meta);
2374
- if (meta !== void 0 && ("outputPreview" in meta || "resultPreview" in meta)) {
2375
- delete meta.outputPreview;
2376
- delete meta.resultPreview;
2377
- }
2378
- const sc = {
2379
- id: acc.id,
2380
- name: acc.name,
2381
- type: acc.type,
2382
- status: mapStepStatus(acc.status),
2383
- durationMs: acc.durationMs,
2384
- error: acc.errorMsg,
2385
- metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
2386
- outputPreview,
2387
- children: []
2388
- };
2389
- nodes.set(acc.id, sc);
2449
+ function projectLogicalEvents(events) {
2450
+ const diagnostics = [];
2451
+ const byRun = /* @__PURE__ */ new Map();
2452
+ for (const event of events) {
2453
+ const list = byRun.get(event.runId) ?? [];
2454
+ list.push(event);
2455
+ byRun.set(event.runId, list);
2390
2456
  }
2391
- const roots = [];
2392
- const sortByOrder = (a, b) => {
2393
- const oa = steps.get(a.id)?.order ?? 0;
2394
- const ob = steps.get(b.id)?.order ?? 0;
2395
- return oa - ob;
2396
- };
2397
- for (const acc of steps.values()) {
2398
- const node = nodes.get(acc.id);
2399
- if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
2400
- nodes.get(acc.parentId).children.push(node);
2401
- } else {
2402
- roots.push(node);
2457
+ const absorbedIds = /* @__PURE__ */ new Set();
2458
+ const logicalByRawId = /* @__PURE__ */ new Map();
2459
+ const stepIdToLogicalId = /* @__PURE__ */ new Map();
2460
+ const logical = [];
2461
+ const orderedRuns = [...byRun.keys()].sort((a, b) => a.localeCompare(b));
2462
+ for (const runId of orderedRuns) {
2463
+ const runEvents = byRun.get(runId) ?? [];
2464
+ const starts = [];
2465
+ const completes = [];
2466
+ const others = [];
2467
+ for (const event of runEvents) {
2468
+ const legacy = legacyEvent(event);
2469
+ if (legacy === "step_started" || legacy === "run_started" && event.status === "running") {
2470
+ starts.push(event);
2471
+ } else if (legacy === "step_completed" || legacy === "run_completed") {
2472
+ completes.push(event);
2473
+ } else {
2474
+ others.push(event);
2475
+ }
2403
2476
  }
2404
- }
2405
- roots.sort(sortByOrder);
2406
- for (const n of nodes.values()) {
2407
- n.children.sort(sortByOrder);
2408
- }
2409
- return {
2410
- runId,
2411
- name: rs.name,
2412
- status: runStatus,
2413
- durationMs,
2414
- steps: roots
2415
- };
2416
- }
2417
-
2418
- // packages/core/src/diff/engine.ts
2419
- var DEFAULT_THRESHOLD_MS = 0;
2420
- function pathSeg(step, index) {
2421
- return { index, name: step.name, stepId: step.id };
2422
- }
2423
- function buildPath(segments) {
2424
- return { path: [...segments] };
2425
- }
2426
- function pairSteps(left, right) {
2427
- const usedRight = /* @__PURE__ */ new Set();
2428
- const pairs = [];
2429
- for (let i = 0; i < left.length; i++) {
2430
- const L = left[i];
2431
- let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
2432
- if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
2433
- const cand = right[i];
2434
- if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
2435
- R = cand;
2477
+ const usedCompletes = /* @__PURE__ */ new Set();
2478
+ for (const start of starts) {
2479
+ const stepId = stepIdOf(start);
2480
+ let match;
2481
+ if (legacyEvent(start) === "run_started") {
2482
+ const candidates = completes.filter(
2483
+ (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "run_completed"
2484
+ );
2485
+ if (candidates.length > 1) {
2486
+ diagnostics.push({
2487
+ code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2488
+ message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
2489
+ eventIds: candidates.map((c) => c.eventId)
2490
+ });
2491
+ candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2492
+ }
2493
+ match = candidates[0];
2494
+ } else if (stepId) {
2495
+ const candidates = completes.filter(
2496
+ (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
2497
+ );
2498
+ if (candidates.length > 1) {
2499
+ diagnostics.push({
2500
+ code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2501
+ message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
2502
+ eventIds: candidates.map((c) => c.eventId)
2503
+ });
2504
+ candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2505
+ }
2506
+ match = candidates[0];
2507
+ }
2508
+ if (match) {
2509
+ usedCompletes.add(match.eventId);
2510
+ absorbedIds.add(match.eventId);
2511
+ const paired = pairStartComplete(start, match);
2512
+ logical.push(paired);
2513
+ logicalByRawId.set(start.eventId, paired);
2514
+ logicalByRawId.set(match.eventId, paired);
2515
+ if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
2516
+ } else {
2517
+ diagnostics.push({
2518
+ code: "AI_LOGICAL_PAIR_UNMATCHED_START",
2519
+ message: `No matching complete for start ${start.eventId}.`,
2520
+ eventIds: [start.eventId]
2521
+ });
2522
+ const alone = asLogical(start);
2523
+ logical.push(alone);
2524
+ logicalByRawId.set(start.eventId, alone);
2525
+ if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2436
2526
  }
2437
2527
  }
2438
- if (R === void 0) {
2439
- R = right.find(
2440
- (r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
2441
- );
2528
+ for (const complete of completes) {
2529
+ if (usedCompletes.has(complete.eventId)) continue;
2530
+ diagnostics.push({
2531
+ code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
2532
+ message: `No matching start for complete ${complete.eventId}.`,
2533
+ eventIds: [complete.eventId]
2534
+ });
2535
+ const alone = asLogical(complete);
2536
+ logical.push(alone);
2537
+ logicalByRawId.set(complete.eventId, alone);
2538
+ const stepId = stepIdOf(complete);
2539
+ if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2540
+ stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2541
+ }
2442
2542
  }
2443
- if (R !== void 0) {
2444
- usedRight.add(R.id);
2445
- pairs.push([L, R]);
2446
- } else {
2447
- pairs.push([L, void 0]);
2543
+ for (const event of others) {
2544
+ const alone = asLogical(event);
2545
+ logical.push(alone);
2546
+ logicalByRawId.set(event.eventId, alone);
2547
+ const stepId = stepIdOf(event);
2548
+ if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2549
+ stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2550
+ }
2448
2551
  }
2449
2552
  }
2450
- for (const R of right) {
2451
- if (!usedRight.has(R.id)) {
2452
- pairs.push([void 0, R]);
2553
+ const logicalById = new Map(logical.map((e) => [e.eventId, e]));
2554
+ const normalized = [];
2555
+ for (const event of logical) {
2556
+ const originalParentId = event.parentId;
2557
+ if (!originalParentId) {
2558
+ normalized.push(event);
2559
+ continue;
2453
2560
  }
2454
- }
2455
- return pairs;
2456
- }
2457
- function compareLeafSteps(L, R, segments, opts, out) {
2458
- const path16 = buildPath(segments);
2459
- if (L.name !== R.name) {
2460
- out.push({
2461
- kind: "structure",
2462
- severity: "warning",
2463
- message: "Step name differs",
2464
- path: path16,
2465
- left: L.name,
2466
- right: R.name
2467
- });
2468
- }
2469
- if ((L.type ?? "") !== (R.type ?? "")) {
2470
- out.push({
2471
- kind: "step-type",
2472
- severity: "warning",
2473
- message: "Step type differs",
2474
- path: path16,
2475
- left: L.type,
2476
- right: R.type
2477
- });
2478
- }
2479
- if ((L.status ?? "") !== (R.status ?? "")) {
2480
- out.push({
2481
- kind: "step-status",
2482
- severity: "warning",
2483
- message: "Step status differs",
2484
- path: path16,
2485
- left: L.status,
2486
- right: R.status
2561
+ let nextParent = originalParentId;
2562
+ let remapped = false;
2563
+ const viaAbsorbed = logicalByRawId.get(originalParentId);
2564
+ if (viaAbsorbed && viaAbsorbed.eventId !== originalParentId) {
2565
+ nextParent = viaAbsorbed.eventId;
2566
+ remapped = true;
2567
+ } else if (!logicalById.has(originalParentId)) {
2568
+ const viaStep = stepIdToLogicalId.get(`${event.runId}:${originalParentId}`);
2569
+ if (viaStep) {
2570
+ nextParent = viaStep;
2571
+ remapped = true;
2572
+ }
2573
+ }
2574
+ if (!remapped) {
2575
+ if (originalParentId === event.eventId) {
2576
+ diagnostics.push({
2577
+ code: "AI_LOGICAL_SELF_PARENT_REMOVED",
2578
+ message: formatProgrammaticDiagnostic(
2579
+ "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2580
+ `Removed self-parent edge on ${event.eventId}.`
2581
+ ),
2582
+ eventIds: [event.eventId]
2583
+ });
2584
+ const { parentId: _drop, ...rest } = event;
2585
+ normalized.push({
2586
+ ...rest,
2587
+ projection: {
2588
+ ...event.projection,
2589
+ parentNormalized: true,
2590
+ originalParentId
2591
+ }
2592
+ });
2593
+ continue;
2594
+ }
2595
+ if (!logicalById.has(originalParentId) && !logicalByRawId.has(originalParentId)) {
2596
+ const mapping = event.attributes?.parentMapping;
2597
+ const unresolved = mapping === "unresolved" || event.attributes?.parentUnresolved === true || event.attributes?.unresolvedParent === true || // LangGraph/framework scaffolding sentinel labels are not event ids.
2598
+ /^LangGraph$/i.test(originalParentId) || originalParentId.startsWith("unresolved:");
2599
+ if (!unresolved) {
2600
+ diagnostics.push({
2601
+ code: "AI_LOGICAL_PARENT_UNRESOLVED",
2602
+ message: `Parent ${originalParentId} unresolved for ${event.eventId}.`,
2603
+ eventIds: [event.eventId]
2604
+ });
2605
+ }
2606
+ }
2607
+ normalized.push(event);
2608
+ continue;
2609
+ }
2610
+ if (nextParent === event.eventId) {
2611
+ diagnostics.push({
2612
+ code: "AI_LOGICAL_SELF_PARENT_REMOVED",
2613
+ message: formatProgrammaticDiagnostic(
2614
+ "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2615
+ `Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
2616
+ ),
2617
+ eventIds: [event.eventId]
2618
+ });
2619
+ const { parentId: _drop, ...rest } = event;
2620
+ normalized.push({
2621
+ ...rest,
2622
+ projection: {
2623
+ ...event.projection,
2624
+ parentNormalized: true,
2625
+ originalParentId
2626
+ }
2627
+ });
2628
+ continue;
2629
+ }
2630
+ diagnostics.push({
2631
+ code: "AI_LOGICAL_PARENT_REMAPPED",
2632
+ message: `Remapped parent ${originalParentId} \u2192 ${nextParent} for ${event.eventId}.`,
2633
+ eventIds: [event.eventId]
2487
2634
  });
2488
- }
2489
- const le = L.error ?? "";
2490
- const re = R.error ?? "";
2491
- if (le !== re) {
2492
- out.push({
2493
- kind: "error",
2494
- severity: "error",
2495
- message: "Step error message differs",
2496
- path: path16,
2497
- left: le || void 0,
2498
- right: re || void 0
2635
+ normalized.push({
2636
+ ...event,
2637
+ parentId: nextParent,
2638
+ projection: {
2639
+ ...event.projection,
2640
+ parentNormalized: true,
2641
+ originalParentId
2642
+ }
2499
2643
  });
2500
2644
  }
2501
- if (!opts.ignoreDuration) {
2502
- const ld = L.durationMs;
2503
- const rd = R.durationMs;
2504
- const th = opts.durationThresholdMs;
2505
- let differs = false;
2506
- if (ld === void 0 && rd === void 0) differs = false;
2507
- else if (ld === void 0 || rd === void 0) differs = true;
2508
- else differs = Math.abs(ld - rd) > th;
2509
- if (differs) {
2510
- out.push({
2511
- kind: "duration",
2512
- severity: "info",
2513
- message: "Step duration differs",
2514
- path: path16,
2515
- left: ld,
2516
- right: rd
2517
- });
2518
- }
2645
+ const rawIndex = new Map(events.map((e, i) => [e.eventId, i]));
2646
+ normalized.sort((a, b) => {
2647
+ const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
2648
+ const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
2649
+ return ai - bi || a.eventId.localeCompare(b.eventId);
2650
+ });
2651
+ return {
2652
+ logicalEvents: Object.freeze(normalized),
2653
+ diagnostics: Object.freeze(diagnostics)
2654
+ };
2655
+ }
2656
+ function resolveCanonicalToolName(event) {
2657
+ const attrs2 = event.attributes;
2658
+ const direct = pickString(attrs2, ["toolName", "tool"]);
2659
+ if (direct) return direct;
2660
+ const metadata = attrs2?.metadata;
2661
+ if (isRecord6(metadata)) {
2662
+ const nested = pickString(metadata, ["toolName", "tool"]);
2663
+ if (nested) return nested;
2519
2664
  }
2520
- const lm = stableJson(L.metadata ?? {});
2521
- const rm = stableJson(R.metadata ?? {});
2522
- if (lm !== rm) {
2523
- out.push({
2524
- kind: "metadata",
2525
- severity: "info",
2526
- message: "Step metadata differs",
2527
- path: path16,
2528
- left: L.metadata,
2529
- right: R.metadata
2530
- });
2665
+ for (const prefix of ["tool:", "function:", "mcp-tools:"]) {
2666
+ if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
2531
2667
  }
2532
- const lo = stableJson(L.outputPreview ?? null);
2533
- const ro = stableJson(R.outputPreview ?? null);
2534
- if (lo !== ro) {
2535
- out.push({
2536
- kind: "output",
2537
- severity: "info",
2538
- message: "Output preview differs",
2539
- path: path16,
2540
- left: L.outputPreview,
2541
- right: R.outputPreview
2542
- });
2668
+ return event.name;
2669
+ }
2670
+ function pickString(record, keys) {
2671
+ if (!record) return void 0;
2672
+ for (const key of keys) {
2673
+ const value = record[key];
2674
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
2543
2675
  }
2676
+ return void 0;
2544
2677
  }
2545
- function compareRecursive(L, R, segments, opts, out) {
2546
- compareLeafSteps(L, R, segments, opts, out);
2547
- const pairs = pairSteps(L.children, R.children);
2548
- let ci = 0;
2549
- for (const [lch, rch] of pairs) {
2550
- if (lch !== void 0 && rch !== void 0) {
2551
- compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
2552
- } else if (lch !== void 0) {
2553
- out.push({
2554
- kind: "step-removed",
2555
- severity: "warning",
2556
- message: `Step only in left run: ${lch.name}`,
2557
- path: buildPath([...segments, pathSeg(lch, ci)]),
2558
- left: lch.id,
2559
- right: void 0
2560
- });
2561
- } else if (rch !== void 0) {
2562
- out.push({
2563
- kind: "step-added",
2564
- severity: "warning",
2565
- message: `Step only in right run: ${rch.name}`,
2566
- path: buildPath([...segments, pathSeg(rch, ci)]),
2567
- left: void 0,
2568
- right: rch.id
2569
- });
2678
+
2679
+ // packages/core/src/checks/derived-failure.ts
2680
+ function isRecord7(value) {
2681
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2682
+ }
2683
+ function pickString2(record, keys) {
2684
+ if (!record) return void 0;
2685
+ for (const key of keys) {
2686
+ const value = record[key];
2687
+ if (typeof value === "string" && value.trim() !== "") return value;
2688
+ }
2689
+ return void 0;
2690
+ }
2691
+ function pickNumber(record, keys) {
2692
+ if (!record) return void 0;
2693
+ for (const key of keys) {
2694
+ const value = record[key];
2695
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
2696
+ return value;
2570
2697
  }
2571
- ci += 1;
2572
2698
  }
2699
+ return void 0;
2573
2700
  }
2574
- function mergeDiffDefaults(options) {
2701
+ function eventMetadata(event) {
2702
+ const attrs2 = isRecord7(event.attributes) ? event.attributes : void 0;
2703
+ const nested = attrs2 !== void 0 && isRecord7(attrs2.metadata) ? attrs2.metadata : void 0;
2575
2704
  return {
2576
- ignoreDuration: false,
2577
- durationThresholdMs: DEFAULT_THRESHOLD_MS,
2578
- focus: "all",
2579
- check: "all"
2705
+ ...attrs2 ?? {},
2706
+ ...nested ?? {}
2580
2707
  };
2581
2708
  }
2582
- function kindMatchesFilter(kind, merged) {
2583
- return true;
2709
+ function canonicalName(event) {
2710
+ if (event.kind === "TOOL") return resolveCanonicalToolName(event);
2711
+ return event.name;
2584
2712
  }
2585
- function diffRuns(left, right, options) {
2586
- const merged = mergeDiffDefaults();
2587
- const opts = {
2588
- ignoreDuration: merged.ignoreDuration,
2589
- durationThresholdMs: merged.durationThresholdMs
2590
- };
2591
- const raw = [];
2592
- if ((left.status ?? "") !== (right.status ?? "")) {
2593
- raw.push({
2594
- kind: "run-status",
2595
- severity: "warning",
2596
- message: "Run completion status differs",
2597
- left: left.status,
2598
- right: right.status
2599
- });
2713
+ function linkKeys(event) {
2714
+ const meta = eventMetadata(event);
2715
+ const keys = [];
2716
+ for (const key of ["linkedStepId", "toolCallId", "mcpToolCallId"]) {
2717
+ const value = pickString2(meta, [key]);
2718
+ if (value !== void 0) keys.push(`${key}:${value}`);
2600
2719
  }
2601
- {
2602
- const ld = left.durationMs;
2603
- const rd = right.durationMs;
2604
- const th = merged.durationThresholdMs;
2605
- let differs = false;
2606
- if (ld === void 0 && rd === void 0) differs = false;
2607
- else if (ld === void 0 || rd === void 0) differs = true;
2608
- else differs = Math.abs(ld - rd) > th;
2609
- if (differs) {
2610
- raw.push({
2611
- kind: "duration",
2612
- severity: "info",
2613
- message: "Run duration differs",
2614
- left: ld,
2615
- right: rd
2616
- });
2720
+ const stepId = pickString2(meta, ["stepId"]);
2721
+ if (stepId !== void 0) keys.push(`stepId:${stepId}`);
2722
+ return keys;
2723
+ }
2724
+ function buildRunContexts(logicalEvents) {
2725
+ const byRun = /* @__PURE__ */ new Map();
2726
+ for (const event of logicalEvents) {
2727
+ const existing = byRun.get(event.runId) ?? {
2728
+ runId: event.runId,
2729
+ name: event.name
2730
+ };
2731
+ const meta = eventMetadata(event);
2732
+ if (event.kind === "RUN" || existing.name === event.runId) {
2733
+ existing.name = event.name || existing.name;
2617
2734
  }
2618
- }
2619
- const pairs = pairSteps(left.steps, right.steps);
2620
- let idx = 0;
2621
- for (const [ls, rs] of pairs) {
2622
- if (ls !== void 0 && rs !== void 0) {
2623
- compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
2624
- idx += 1;
2625
- } else if (ls !== void 0) {
2626
- raw.push({
2627
- kind: "step-removed",
2628
- severity: "warning",
2629
- message: `Step only in left run: ${ls.name}`,
2630
- path: buildPath([pathSeg(ls, idx)]),
2631
- left: ls.id,
2632
- right: void 0
2633
- });
2634
- idx += 1;
2635
- } else if (rs !== void 0) {
2636
- raw.push({
2637
- kind: "step-added",
2638
- severity: "warning",
2639
- message: `Step only in right run: ${rs.name}`,
2640
- path: buildPath([pathSeg(rs, idx)]),
2641
- left: void 0,
2642
- right: rs.id
2643
- });
2644
- idx += 1;
2735
+ if (event.kind === "RUN" && event.status !== void 0 && event.status !== "running") {
2736
+ existing.status = event.status;
2645
2737
  }
2738
+ existing.retryOf ??= pickString2(meta, ["retryOf"]);
2739
+ existing.attempt ??= pickNumber(meta, ["attempt", "retryAttempt", "retryCount"]);
2740
+ existing.sessionId ??= pickString2(meta, ["sessionId", "conversationId"]);
2741
+ existing.groupId ??= pickString2(meta, ["groupId"]);
2742
+ existing.parentGroupId ??= pickString2(meta, ["parentGroupId"]);
2743
+ existing.fallbackOf ??= pickString2(meta, ["fallbackOf", "fallbackFrom"]);
2744
+ byRun.set(event.runId, existing);
2646
2745
  }
2647
- const differences = raw.filter((d) => kindMatchesFilter(d.kind));
2648
- let errors = 0;
2649
- let warnings = 0;
2650
- let info = 0;
2651
- for (const d of differences) {
2652
- if (d.severity === "error") errors += 1;
2653
- else if (d.severity === "warning") warnings += 1;
2654
- else info += 1;
2655
- }
2656
- const firstVisible = differences[0];
2657
- const firstDivergence = firstVisible !== void 0 ? {
2658
- kind: "first-divergence",
2659
- severity: firstVisible.severity,
2660
- message: `First divergence: ${firstVisible.message}`,
2661
- path: firstVisible.path,
2662
- left: firstVisible.left,
2663
- right: firstVisible.right
2664
- } : void 0;
2665
- const summary = {
2666
- leftRunId: left.runId,
2667
- rightRunId: right.runId,
2668
- totalDifferences: differences.length,
2669
- errors,
2670
- warnings,
2671
- info,
2672
- firstDivergence
2673
- };
2674
- return { summary, differences };
2746
+ return byRun;
2675
2747
  }
2676
-
2677
- // packages/core/src/evidence/zip.ts
2678
- (() => {
2679
- const table = new Uint32Array(256);
2680
- for (let n = 0; n < 256; n += 1) {
2681
- let c = n;
2682
- for (let k = 0; k < 8; k += 1) {
2683
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
2684
- }
2685
- table[n] = c >>> 0;
2686
- }
2687
- return table;
2688
- })();
2689
-
2690
- // packages/core/src/diagnostics/programmatic.ts
2691
- var PROGRAMMATIC_DIAGNOSTIC_SPECS = Object.freeze({
2692
- AI_TRACE_INPUT_INVALID: {
2693
- code: "AI_TRACE_INPUT_INVALID",
2694
- summary: 'Expected { type: "file", path }, { type: "directory", path }, { type: "string", content }, { type: "buffer", content }, or { type: "stdin" }.',
2695
- remediation: "For a file path, use openTraceFile(path).",
2696
- relatedCodes: ["invalid_input"]
2697
- },
2698
- AI_TRACE_FORMAT_UNSUPPORTED: {
2699
- code: "AI_TRACE_FORMAT_UNSUPPORTED",
2700
- summary: "No trace reader could detect the input format.",
2701
- remediation: "Pass an AgentInspect JSONL file via openTraceFile, or set options.format to a registered reader.",
2702
- relatedCodes: ["unsupported_format"]
2703
- },
2704
- AI_TRACE_FORMAT_AMBIGUOUS: {
2705
- code: "AI_TRACE_FORMAT_AMBIGUOUS",
2706
- summary: "Multiple trace readers matched the input with equal confidence.",
2707
- remediation: "Set options.format explicitly to disambiguate the reader.",
2708
- relatedCodes: ["ambiguous_format"]
2709
- },
2710
- AI_TRACE_FACTS_INPUT_NOT_NORMALIZED: {
2711
- code: "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED",
2712
- summary: "TraceFacts requires TraceReadResult or PersistedInspectEvent[].",
2713
- remediation: "Use openTraceFile() to normalize a JSONL trace first."
2714
- },
2715
- AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED: {
2716
- code: "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
2717
- summary: "Multiple runs are available; select a run before executing checks.",
2718
- remediation: "Pass options.runId or TraceCheckInput.selectedRun.",
2719
- relatedCodes: ["AI_CHECK_RUN_SELECTION_REQUIRED"]
2720
- },
2721
- AI_TRACE_RELATIONSHIP_SELF_PARENT: {
2722
- code: "AI_TRACE_RELATIONSHIP_SELF_PARENT",
2723
- summary: "A parentId equals its own event/step id.",
2724
- remediation: "Reject at capture (AI_LANGGRAPH_SELF_PARENT_REJECTED) or drop via logical projection; do not invent replacement parents.",
2725
- relatedCodes: [
2726
- "AI_LANGGRAPH_SELF_PARENT_REJECTED",
2727
- "AI_LOGICAL_SELF_PARENT_REMOVED"
2728
- ]
2729
- },
2730
- AI_TRACE_RELATIONSHIP_CYCLE: {
2731
- code: "AI_TRACE_RELATIONSHIP_CYCLE",
2732
- summary: "Trace contains a parentId cycle.",
2733
- remediation: "Use visibility-first tree linking for legacy fixtures; prefer acyclic capture for new adapter output.",
2734
- relatedCodes: ["structure.cycle"]
2748
+ function sameCorrelationScope(a, b) {
2749
+ if (a.sessionId && b.sessionId && a.sessionId === b.sessionId) return true;
2750
+ if (a.groupId && b.groupId && a.groupId === b.groupId) return true;
2751
+ if (a.parentGroupId && b.parentGroupId && a.parentGroupId === b.parentGroupId) {
2752
+ return true;
2735
2753
  }
2736
- });
2737
- function formatProgrammaticDiagnostic(code, detail) {
2738
- const spec = PROGRAMMATIC_DIAGNOSTIC_SPECS[code];
2739
- const summary = detail?.trim() ? detail.trim() : spec.summary;
2740
- return `${code}: ${summary} Remediation: ${spec.remediation}`;
2754
+ return false;
2741
2755
  }
2742
-
2743
- // packages/core/src/safety/sensitive-key.ts
2744
- function keyHasExplicitSeparator(value) {
2745
- return /[_\-.]/.test(value);
2756
+ function isSuccessful(event) {
2757
+ return event.status === "ok";
2746
2758
  }
2747
- function normalizeSensitiveKey(value) {
2748
- return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
2759
+ function isFailure(event) {
2760
+ return event.status === "error";
2749
2761
  }
2750
- var NON_CREDENTIAL_TOKEN_CONFIG_KEYS = new Set(
2751
- [
2752
- "tokens",
2753
- "max_tokens",
2754
- "min_tokens",
2755
- "ls_max_tokens",
2756
- "token_count",
2757
- "token_limit",
2758
- "token_budget",
2759
- "input_tokens",
2760
- "output_tokens",
2761
- "total_tokens",
2762
- "cached_tokens",
2763
- "prompt_tokens",
2764
- "completion_tokens"
2765
- ].map(normalizeSensitiveKey)
2766
- );
2767
- var DEFAULT_CREDENTIAL_SENSITIVE_KEYS = [
2768
- "authorization",
2769
- "cookie",
2770
- "token",
2771
- "access_token",
2772
- "accesstoken",
2773
- "auth_token",
2774
- "authtoken",
2775
- "refresh_token",
2776
- "refreshtoken",
2777
- "id_token",
2778
- "idtoken",
2779
- "bearer_token",
2780
- "bearertoken",
2781
- "api_token",
2782
- "apitoken",
2783
- "apikey",
2784
- "api_key",
2785
- "password",
2786
- "secret",
2787
- "email"
2788
- ];
2789
- function isTokenCredentialKey(normalized) {
2790
- if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
2791
- if (normalized === "token") return true;
2792
- if (normalized.endsWith("tokens")) return false;
2793
- return normalized.endsWith("token");
2762
+ function compareEventOrder(a, b) {
2763
+ const byTime = a.timestamp.localeCompare(b.timestamp);
2764
+ if (byTime !== 0) return byTime;
2765
+ return a.eventId.localeCompare(b.eventId);
2794
2766
  }
2795
- function isCredentialSensitiveKey(key, sensitiveKeys = DEFAULT_CREDENTIAL_SENSITIVE_KEYS) {
2796
- if (!key) return false;
2797
- const normalized = normalizeSensitiveKey(key);
2798
- if (!normalized) return false;
2799
- if (NON_CREDENTIAL_TOKEN_CONFIG_KEYS.has(normalized)) return false;
2800
- const allowPrefixCompound = keyHasExplicitSeparator(key);
2801
- for (const sensitive of sensitiveKeys) {
2802
- const s = normalizeSensitiveKey(sensitive);
2803
- if (!s) continue;
2804
- if (s === "token") {
2805
- if (isTokenCredentialKey(normalized)) return true;
2767
+ function collectCandidates(failure, logicalEvents, runs) {
2768
+ const failureRun = runs.get(failure.runId);
2769
+ const failureLinks = new Set(linkKeys(failure));
2770
+ const failureName = canonicalName(failure);
2771
+ const failureAttempt = pickNumber(eventMetadata(failure), ["attempt", "retryAttempt", "retryCount"]) ?? failureRun?.attempt;
2772
+ const candidates = [];
2773
+ for (const event of logicalEvents) {
2774
+ if (event.eventId === failure.eventId) continue;
2775
+ if (event.status === "running") continue;
2776
+ const eventRun = runs.get(event.runId);
2777
+ const eventMeta = eventMetadata(event);
2778
+ const sameRun = event.runId === failure.runId;
2779
+ if (eventRun?.retryOf === failure.runId) {
2780
+ candidates.push({
2781
+ event,
2782
+ basis: "retryOf",
2783
+ confidence: "explicit",
2784
+ viaRunId: event.runId
2785
+ });
2806
2786
  continue;
2807
2787
  }
2808
- if (normalized === s) return true;
2809
- if (normalized.endsWith(`_${s}`)) return true;
2810
- if (allowPrefixCompound && normalized.startsWith(`${s}_`)) return true;
2811
- }
2812
- return false;
2813
- }
2814
-
2815
- // packages/core/src/checks/logical-events.ts
2816
- function isRecord6(value) {
2817
- return typeof value === "object" && value !== null && !Array.isArray(value);
2818
- }
2819
- function legacyEvent(event) {
2820
- const value = event.attributes?.legacyEvent;
2821
- return typeof value === "string" ? value : void 0;
2822
- }
2823
- function stepIdOf(event) {
2824
- const value = event.attributes?.stepId;
2825
- if (typeof value === "string" && value.trim() !== "") return value;
2826
- return void 0;
2788
+ if (eventRun?.fallbackOf === failure.runId || pickString2(eventMeta, ["fallbackOf", "fallbackFrom"]) === failure.runId) {
2789
+ candidates.push({
2790
+ event,
2791
+ basis: "fallbackOf",
2792
+ confidence: "explicit",
2793
+ viaRunId: event.runId
2794
+ });
2795
+ continue;
2796
+ }
2797
+ if (sameRun && compareEventOrder(failure, event) >= 0) continue;
2798
+ const eventLinks = linkKeys(event);
2799
+ const sharedLink = eventLinks.find((key) => failureLinks.has(key));
2800
+ if (sharedLink !== void 0) {
2801
+ candidates.push({
2802
+ event,
2803
+ basis: sharedLink.split(":")[0] ?? "linkedId",
2804
+ confidence: "explicit"
2805
+ });
2806
+ continue;
2807
+ }
2808
+ const eventAttempt = pickNumber(eventMeta, ["attempt", "retryAttempt", "retryCount"]) ?? eventRun?.attempt;
2809
+ const sameParent = failure.parentId !== void 0 && event.parentId !== void 0 && failure.parentId === event.parentId;
2810
+ const differentParent = failure.parentId !== void 0 && event.parentId !== void 0 && failure.parentId !== event.parentId;
2811
+ const sessionScoped = failureRun !== void 0 && eventRun !== void 0 && sameCorrelationScope(failureRun, eventRun);
2812
+ if (canonicalName(event) === failureName && failureAttempt !== void 0 && eventAttempt !== void 0 && eventAttempt > failureAttempt && !differentParent && (sameParent || sessionScoped || sameRun)) {
2813
+ candidates.push({
2814
+ event,
2815
+ basis: "attempt-progression",
2816
+ confidence: "correlated",
2817
+ ...event.runId !== failure.runId ? { viaRunId: event.runId } : {}
2818
+ });
2819
+ }
2820
+ }
2821
+ const byId = /* @__PURE__ */ new Map();
2822
+ for (const candidate of candidates) {
2823
+ const prev = byId.get(candidate.event.eventId);
2824
+ if (!prev || prev.confidence !== "explicit" && candidate.confidence === "explicit") {
2825
+ byId.set(candidate.event.eventId, candidate);
2826
+ }
2827
+ }
2828
+ return [...byId.values()].sort((a, b) => compareEventOrder(a.event, b.event));
2827
2829
  }
2828
- function cloneEvent(event) {
2830
+ function classifyFailure(failure, candidates, runs, logicalEvents) {
2831
+ const successful = candidates.filter((c) => isSuccessful(c.event));
2832
+ const unsuccessful = candidates.filter((c) => !isSuccessful(c.event));
2833
+ const retryRunIds = Object.freeze(
2834
+ [...new Set(candidates.map((c) => c.viaRunId).filter((id) => id !== void 0))].sort(
2835
+ (a, b) => a.localeCompare(b)
2836
+ )
2837
+ );
2838
+ if (successful.length > 1) {
2839
+ const distinctRuns = new Set(successful.map((c) => c.event.runId));
2840
+ const distinctParents = new Set(
2841
+ successful.map((c) => c.event.parentId ?? "").filter((id) => id !== "")
2842
+ );
2843
+ if (distinctRuns.size > 1 || distinctParents.size > 1) {
2844
+ return {
2845
+ eventId: failure.eventId,
2846
+ runId: failure.runId,
2847
+ name: failure.name,
2848
+ kind: failure.kind,
2849
+ role: "unknown",
2850
+ confidence: "unknown",
2851
+ basis: Object.freeze(["ambiguous-recovery-candidates"]),
2852
+ recoveryEventIds: Object.freeze(
2853
+ successful.map((c) => c.event.eventId).sort((a, b) => a.localeCompare(b))
2854
+ ),
2855
+ retryRunIds
2856
+ };
2857
+ }
2858
+ }
2859
+ if (successful.length >= 1) {
2860
+ const best = successful[0];
2861
+ return {
2862
+ eventId: failure.eventId,
2863
+ runId: failure.runId,
2864
+ name: failure.name,
2865
+ kind: failure.kind,
2866
+ role: "recovered",
2867
+ confidence: best.confidence,
2868
+ basis: Object.freeze([best.basis]),
2869
+ recoveryEventIds: Object.freeze(
2870
+ successful.map((c) => c.event.eventId).sort((a, b) => a.localeCompare(b))
2871
+ ),
2872
+ retryRunIds
2873
+ };
2874
+ }
2875
+ if (candidates.length > 0) {
2876
+ const best = candidates[0];
2877
+ return {
2878
+ eventId: failure.eventId,
2879
+ runId: failure.runId,
2880
+ name: failure.name,
2881
+ kind: failure.kind,
2882
+ role: "transient",
2883
+ confidence: best.confidence,
2884
+ basis: Object.freeze([
2885
+ best.basis,
2886
+ unsuccessful.some((c) => c.event.status === void 0) ? "retry-incomplete" : "retry-without-success"
2887
+ ]),
2888
+ recoveryEventIds: Object.freeze([]),
2889
+ retryRunIds
2890
+ };
2891
+ }
2892
+ const failureMeta = eventMetadata(failure);
2893
+ const declaredSuccessor = pickString2(failureMeta, [
2894
+ "retriedBy",
2895
+ "nextRetryRunId",
2896
+ "retryRunId"
2897
+ ]);
2898
+ if (declaredSuccessor !== void 0 && !runs.has(declaredSuccessor)) {
2899
+ return {
2900
+ eventId: failure.eventId,
2901
+ runId: failure.runId,
2902
+ name: failure.name,
2903
+ kind: failure.kind,
2904
+ role: "transient",
2905
+ confidence: "explicit",
2906
+ basis: Object.freeze(["retry-declared", "retry-run-missing"]),
2907
+ recoveryEventIds: Object.freeze([]),
2908
+ retryRunIds: Object.freeze([declaredSuccessor])
2909
+ };
2910
+ }
2911
+ for (const run of runs.values()) {
2912
+ if (run.retryOf === failure.runId) {
2913
+ return {
2914
+ eventId: failure.eventId,
2915
+ runId: failure.runId,
2916
+ name: failure.name,
2917
+ kind: failure.kind,
2918
+ role: "transient",
2919
+ confidence: "explicit",
2920
+ basis: Object.freeze(["retryOf", "retry-run-missing-or-empty"]),
2921
+ recoveryEventIds: Object.freeze([]),
2922
+ retryRunIds: Object.freeze([run.runId])
2923
+ };
2924
+ }
2925
+ }
2926
+ const failureRun = runs.get(failure.runId);
2927
+ const hasSuccessorDeclared = [...runs.values()].some((run) => run.retryOf === failure.runId);
2928
+ const isFinalInChain = failureRun !== void 0 && !hasSuccessorDeclared && (failureRun.retryOf !== void 0 || failureRun.attempt !== void 0 && failureRun.attempt > 1 || pickNumber(eventMetadata(failure), ["attempt"]) !== void 0);
2929
+ if (isFinalInChain && failureRun?.status === "error" && !logicalEvents.some(
2930
+ (event) => event.runId === failure.runId && event.eventId !== failure.eventId && isSuccessful(event) && canonicalName(event) === canonicalName(failure)
2931
+ )) {
2932
+ return {
2933
+ eventId: failure.eventId,
2934
+ runId: failure.runId,
2935
+ name: failure.name,
2936
+ kind: failure.kind,
2937
+ role: "terminal",
2938
+ confidence: failureRun.retryOf !== void 0 ? "explicit" : "correlated",
2939
+ basis: Object.freeze(["final-retry-chain-member", "enclosing-run-error"]),
2940
+ recoveryEventIds: Object.freeze([]),
2941
+ retryRunIds: Object.freeze(
2942
+ failureRun.retryOf !== void 0 ? [failureRun.retryOf] : []
2943
+ )
2944
+ };
2945
+ }
2829
2946
  return {
2830
- ...event,
2831
- ...event.attributes !== void 0 ? { attributes: { ...event.attributes } } : {},
2832
- ...event.error !== void 0 ? { error: { ...event.error } } : {},
2833
- ...event.tokenUsage !== void 0 ? { tokenUsage: { ...event.tokenUsage } } : {},
2834
- ...event.source !== void 0 ? { source: { ...event.source } } : {}
2947
+ eventId: failure.eventId,
2948
+ runId: failure.runId,
2949
+ name: failure.name,
2950
+ kind: failure.kind,
2951
+ role: "unknown",
2952
+ confidence: "unknown",
2953
+ basis: Object.freeze(["no-explicit-or-correlated-recovery"]),
2954
+ recoveryEventIds: Object.freeze([]),
2955
+ retryRunIds: Object.freeze([])
2835
2956
  };
2836
2957
  }
2837
- function mergeAttributes(start, complete) {
2838
- const merged = {
2839
- ...isRecord6(complete.attributes) ? complete.attributes : {},
2840
- ...isRecord6(start.attributes) ? start.attributes : {}
2841
- };
2842
- merged.legacyEvent = start.attributes?.legacyEvent ?? complete.attributes?.legacyEvent;
2843
- merged.legacyCompleteEvent = complete.attributes?.legacyEvent;
2844
- if (complete.attributes?.errorStack !== void 0) {
2845
- merged.errorStack = complete.attributes.errorStack;
2958
+ function deriveFailureFacts(logicalEvents) {
2959
+ const runs = buildRunContexts(logicalEvents);
2960
+ const failures = logicalEvents.filter((event) => isFailure(event)).sort(compareEventOrder);
2961
+ const failureFacts = failures.map(
2962
+ (failure) => classifyFailure(failure, collectCandidates(failure, logicalEvents, runs), runs, logicalEvents)
2963
+ );
2964
+ const byRole = /* @__PURE__ */ new Map([
2965
+ ["transient", []],
2966
+ ["recovered", []],
2967
+ ["terminal", []],
2968
+ ["unknown", []]
2969
+ ]);
2970
+ for (const fact of failureFacts) {
2971
+ byRole.get(fact.role).push(fact);
2846
2972
  }
2847
- return Object.keys(merged).length > 0 ? merged : void 0;
2848
- }
2849
- function pairStartComplete(start, complete) {
2850
- const attributes = mergeAttributes(start, complete);
2851
- const paired = {
2852
- ...cloneEvent(start),
2853
- status: complete.status,
2854
- timestamp: complete.timestamp ?? start.timestamp,
2855
- ...complete.endedAt !== void 0 ? { endedAt: complete.endedAt } : {},
2856
- ...complete.durationMs !== void 0 ? { durationMs: complete.durationMs } : {},
2857
- ...complete.error !== void 0 ? { error: { ...complete.error } } : {},
2858
- ...complete.tokenUsage !== void 0 && start.tokenUsage === void 0 ? { tokenUsage: { ...complete.tokenUsage } } : {},
2859
- ...attributes !== void 0 ? { attributes } : {}
2973
+ for (const [role, list] of byRole) {
2974
+ byRole.set(
2975
+ role,
2976
+ Object.freeze(
2977
+ [...list].sort((a, b) => {
2978
+ const byRun = a.runId.localeCompare(b.runId);
2979
+ if (byRun !== 0) return byRun;
2980
+ return a.eventId.localeCompare(b.eventId);
2981
+ })
2982
+ )
2983
+ );
2984
+ }
2985
+ const failureRoleCounts = {
2986
+ transient: byRole.get("transient").length,
2987
+ recovered: byRole.get("recovered").length,
2988
+ terminal: byRole.get("terminal").length,
2989
+ unknown: byRole.get("unknown").length
2860
2990
  };
2861
2991
  return {
2862
- ...paired,
2863
- sourceEventIds: Object.freeze([start.eventId, complete.eventId]),
2864
- projection: {
2865
- paired: true,
2866
- absorbedEventIds: Object.freeze([complete.eventId]),
2867
- parentNormalized: false,
2868
- ...start.parentId !== void 0 ? { originalParentId: start.parentId } : {}
2869
- }
2992
+ failureFacts: Object.freeze(failureFacts),
2993
+ failuresByRole: byRole,
2994
+ failureRoleCounts
2870
2995
  };
2871
2996
  }
2872
- function asLogical(event, extras) {
2873
- return {
2874
- ...cloneEvent(event),
2875
- sourceEventIds: Object.freeze([event.eventId]),
2876
- projection: {
2877
- paired: false,
2878
- absorbedEventIds: Object.freeze([]),
2879
- parentNormalized: extras?.parentNormalized === true,
2880
- ...{}
2881
- }
2882
- };
2997
+
2998
+ // packages/core/src/checks/relationship-facts.ts
2999
+ function attrs(event) {
3000
+ return event.attributes && typeof event.attributes === "object" ? event.attributes : {};
2883
3001
  }
2884
- function projectLogicalEvents(events) {
3002
+ function stringAttr(record, key) {
3003
+ const value = record[key];
3004
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
3005
+ }
3006
+ function deriveRelationshipFacts(events) {
3007
+ const relationships = [];
2885
3008
  const diagnostics = [];
2886
- const byRun = /* @__PURE__ */ new Map();
3009
+ const byId = new Map(events.map((event) => [event.eventId, event]));
3010
+ const seen = /* @__PURE__ */ new Set();
3011
+ const push = (edge) => {
3012
+ const key = `${edge.type}:${edge.fromEventId}:${edge.toEventId ?? ""}:${edge.externalRef ?? ""}`;
3013
+ if (seen.has(key)) return;
3014
+ seen.add(key);
3015
+ relationships.push(edge);
3016
+ };
2887
3017
  for (const event of events) {
2888
- const list = byRun.get(event.runId) ?? [];
2889
- list.push(event);
2890
- byRun.set(event.runId, list);
2891
- }
2892
- const absorbedIds = /* @__PURE__ */ new Set();
2893
- const logicalByRawId = /* @__PURE__ */ new Map();
2894
- const stepIdToLogicalId = /* @__PURE__ */ new Map();
2895
- const logical = [];
2896
- const orderedRuns = [...byRun.keys()].sort((a, b) => a.localeCompare(b));
2897
- for (const runId of orderedRuns) {
2898
- const runEvents = byRun.get(runId) ?? [];
2899
- const starts = [];
2900
- const completes = [];
2901
- const others = [];
2902
- for (const event of runEvents) {
2903
- const legacy = legacyEvent(event);
2904
- if (legacy === "step_started" || legacy === "run_started" && event.status === "running") {
2905
- starts.push(event);
2906
- } else if (legacy === "step_completed" || legacy === "run_completed") {
2907
- completes.push(event);
2908
- } else {
2909
- others.push(event);
2910
- }
2911
- }
2912
- const usedCompletes = /* @__PURE__ */ new Set();
2913
- for (const start of starts) {
2914
- const stepId = stepIdOf(start);
2915
- let match;
2916
- if (legacyEvent(start) === "run_started") {
2917
- const candidates = completes.filter(
2918
- (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "run_completed"
2919
- );
2920
- if (candidates.length > 1) {
2921
- diagnostics.push({
2922
- code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2923
- message: `Multiple run_completed rows for run ${runId}; using first by eventId.`,
2924
- eventIds: candidates.map((c) => c.eventId)
2925
- });
2926
- candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2927
- }
2928
- match = candidates[0];
2929
- } else if (stepId) {
2930
- const candidates = completes.filter(
2931
- (c) => !usedCompletes.has(c.eventId) && legacyEvent(c) === "step_completed" && stepIdOf(c) === stepId
2932
- );
2933
- if (candidates.length > 1) {
2934
- diagnostics.push({
2935
- code: "AI_LOGICAL_PAIR_AMBIGUOUS",
2936
- message: `Multiple step_completed rows for stepId ${stepId}; using first by eventId.`,
2937
- eventIds: candidates.map((c) => c.eventId)
2938
- });
2939
- candidates.sort((a, b) => a.eventId.localeCompare(b.eventId));
2940
- }
2941
- match = candidates[0];
2942
- }
2943
- if (match) {
2944
- usedCompletes.add(match.eventId);
2945
- absorbedIds.add(match.eventId);
2946
- const paired = pairStartComplete(start, match);
2947
- logical.push(paired);
2948
- logicalByRawId.set(start.eventId, paired);
2949
- logicalByRawId.set(match.eventId, paired);
2950
- if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, paired.eventId);
2951
- } else {
3018
+ if (event.parentId) {
3019
+ push({
3020
+ type: "parent-child",
3021
+ fromEventId: event.parentId,
3022
+ toEventId: event.eventId,
3023
+ confidence: byId.has(event.parentId) ? "explicit" : "unknown",
3024
+ basis: ["persisted.parentId"]
3025
+ });
3026
+ if (!byId.has(event.parentId)) {
2952
3027
  diagnostics.push({
2953
- code: "AI_LOGICAL_PAIR_UNMATCHED_START",
2954
- message: `No matching complete for start ${start.eventId}.`,
2955
- eventIds: [start.eventId]
3028
+ code: "AI_RELATIONSHIP_PARENT_MISSING",
3029
+ message: `parentId ${event.parentId} is not present in the event set.`,
3030
+ eventId: event.eventId
2956
3031
  });
2957
- const alone = asLogical(start);
2958
- logical.push(alone);
2959
- logicalByRawId.set(start.eventId, alone);
2960
- if (stepId) stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2961
3032
  }
2962
3033
  }
2963
- for (const complete of completes) {
2964
- if (usedCompletes.has(complete.eventId)) continue;
2965
- diagnostics.push({
2966
- code: "AI_LOGICAL_PAIR_UNMATCHED_COMPLETE",
2967
- message: `No matching start for complete ${complete.eventId}.`,
2968
- eventIds: [complete.eventId]
3034
+ const bag = attrs(event);
3035
+ const meta = extractSessionWorkflowMetadata(bag) ?? {};
3036
+ const nested = bag.metadata && typeof bag.metadata === "object" ? extractSessionWorkflowMetadata(bag.metadata) : void 0;
3037
+ const workflow = { ...meta, ...nested };
3038
+ if (workflow.retryOf) {
3039
+ const target = events.find((candidate) => candidate.runId === workflow.retryOf);
3040
+ push({
3041
+ type: "retry-of",
3042
+ fromEventId: event.eventId,
3043
+ ...target ? { toEventId: target.eventId } : {},
3044
+ externalRef: workflow.retryOf,
3045
+ confidence: target ? "explicit" : "correlated",
3046
+ basis: ["attributes.retryOf"]
2969
3047
  });
2970
- const alone = asLogical(complete);
2971
- logical.push(alone);
2972
- logicalByRawId.set(complete.eventId, alone);
2973
- const stepId = stepIdOf(complete);
2974
- if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2975
- stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2976
- }
2977
- }
2978
- for (const event of others) {
2979
- const alone = asLogical(event);
2980
- logical.push(alone);
2981
- logicalByRawId.set(event.eventId, alone);
2982
- const stepId = stepIdOf(event);
2983
- if (stepId && !stepIdToLogicalId.has(`${runId}:${stepId}`)) {
2984
- stepIdToLogicalId.set(`${runId}:${stepId}`, alone.eventId);
2985
- }
2986
3048
  }
2987
- }
2988
- const logicalById = new Map(logical.map((e) => [e.eventId, e]));
2989
- const normalized = [];
2990
- for (const event of logical) {
2991
- const originalParentId = event.parentId;
2992
- if (!originalParentId) {
2993
- normalized.push(event);
2994
- continue;
3049
+ const attemptOf = stringAttr(bag, "attemptOf") ?? stringAttr(bag, "operationId");
3050
+ if (attemptOf && workflow.attempt !== void 0) {
3051
+ push({
3052
+ type: "attempt-of",
3053
+ fromEventId: event.eventId,
3054
+ externalRef: attemptOf,
3055
+ confidence: "explicit",
3056
+ basis: workflow.attempt !== void 0 ? ["attributes.attempt", "attributes.operationId"] : ["attributes.operationId"]
3057
+ });
2995
3058
  }
2996
- let nextParent = originalParentId;
2997
- let remapped = false;
2998
- const viaAbsorbed = logicalByRawId.get(originalParentId);
2999
- if (viaAbsorbed && viaAbsorbed.eventId !== originalParentId) {
3000
- nextParent = viaAbsorbed.eventId;
3001
- remapped = true;
3002
- } else if (!logicalById.has(originalParentId)) {
3003
- const viaStep = stepIdToLogicalId.get(`${event.runId}:${originalParentId}`);
3004
- if (viaStep) {
3005
- nextParent = viaStep;
3006
- remapped = true;
3007
- }
3059
+ const fallbackOf = stringAttr(bag, "fallbackOf");
3060
+ if (fallbackOf) {
3061
+ push({
3062
+ type: "fallback-of",
3063
+ fromEventId: event.eventId,
3064
+ externalRef: fallbackOf,
3065
+ confidence: "explicit",
3066
+ basis: ["attributes.fallbackOf"]
3067
+ });
3008
3068
  }
3009
- if (!remapped) {
3010
- if (originalParentId === event.eventId) {
3011
- diagnostics.push({
3012
- code: "AI_LOGICAL_SELF_PARENT_REMOVED",
3013
- message: formatProgrammaticDiagnostic(
3014
- "AI_TRACE_RELATIONSHIP_SELF_PARENT",
3015
- `Removed self-parent edge on ${event.eventId}.`
3016
- ),
3017
- eventIds: [event.eventId]
3018
- });
3019
- const { parentId: _drop, ...rest } = event;
3020
- normalized.push({
3021
- ...rest,
3022
- projection: {
3023
- ...event.projection,
3024
- parentNormalized: true,
3025
- originalParentId
3026
- }
3027
- });
3028
- continue;
3029
- }
3030
- if (!logicalById.has(originalParentId) && !logicalByRawId.has(originalParentId)) {
3031
- const mapping = event.attributes?.parentMapping;
3032
- const unresolved = mapping === "unresolved" || event.attributes?.parentUnresolved === true || event.attributes?.unresolvedParent === true || // LangGraph/framework scaffolding sentinel labels are not event ids.
3033
- /^LangGraph$/i.test(originalParentId) || originalParentId.startsWith("unresolved:");
3034
- if (!unresolved) {
3035
- diagnostics.push({
3036
- code: "AI_LOGICAL_PARENT_UNRESOLVED",
3037
- message: `Parent ${originalParentId} unresolved for ${event.eventId}.`,
3038
- eventIds: [event.eventId]
3039
- });
3040
- }
3041
- }
3042
- normalized.push(event);
3043
- continue;
3069
+ const remediationOf = stringAttr(bag, "remediationOf");
3070
+ if (remediationOf) {
3071
+ push({
3072
+ type: "remediation-of",
3073
+ fromEventId: event.eventId,
3074
+ externalRef: remediationOf,
3075
+ confidence: "explicit",
3076
+ basis: ["attributes.remediationOf"]
3077
+ });
3044
3078
  }
3045
- if (nextParent === event.eventId) {
3046
- diagnostics.push({
3047
- code: "AI_LOGICAL_SELF_PARENT_REMOVED",
3048
- message: formatProgrammaticDiagnostic(
3049
- "AI_TRACE_RELATIONSHIP_SELF_PARENT",
3050
- `Removed self-parent edge on ${event.eventId} (was ${originalParentId}).`
3051
- ),
3052
- eventIds: [event.eventId]
3079
+ const evidenceFor = stringAttr(bag, "evidenceFor");
3080
+ if (evidenceFor) {
3081
+ push({
3082
+ type: "evidence-for",
3083
+ fromEventId: event.eventId,
3084
+ ...byId.has(evidenceFor) ? { toEventId: evidenceFor } : { externalRef: evidenceFor },
3085
+ confidence: byId.has(evidenceFor) ? "explicit" : "correlated",
3086
+ basis: ["attributes.evidenceFor"]
3053
3087
  });
3054
- const { parentId: _drop, ...rest } = event;
3055
- normalized.push({
3056
- ...rest,
3057
- projection: {
3058
- ...event.projection,
3059
- parentNormalized: true,
3060
- originalParentId
3061
- }
3088
+ }
3089
+ const acceptedBy = stringAttr(bag, "acceptedBy");
3090
+ if (acceptedBy) {
3091
+ push({
3092
+ type: "accepted-by",
3093
+ fromEventId: event.eventId,
3094
+ ...byId.has(acceptedBy) ? { toEventId: acceptedBy } : { externalRef: acceptedBy },
3095
+ confidence: byId.has(acceptedBy) ? "explicit" : "correlated",
3096
+ basis: ["attributes.acceptedBy"]
3097
+ });
3098
+ }
3099
+ const supersedes = stringAttr(bag, "supersedes");
3100
+ if (supersedes) {
3101
+ push({
3102
+ type: "supersedes",
3103
+ fromEventId: event.eventId,
3104
+ ...byId.has(supersedes) ? { toEventId: supersedes } : { externalRef: supersedes },
3105
+ confidence: byId.has(supersedes) ? "explicit" : "correlated",
3106
+ basis: ["attributes.supersedes"]
3107
+ });
3108
+ }
3109
+ const sourceLineage = stringAttr(bag, "sourceLineage") ?? stringAttr(bag, "sourceEventId");
3110
+ if (sourceLineage) {
3111
+ push({
3112
+ type: "source-lineage",
3113
+ fromEventId: event.eventId,
3114
+ externalRef: sourceLineage,
3115
+ confidence: "explicit",
3116
+ basis: ["attributes.sourceLineage"]
3117
+ });
3118
+ }
3119
+ const unsupported = stringAttr(bag, "relationshipType");
3120
+ if (unsupported && unsupported !== "parent-child" && ![
3121
+ "source-lineage",
3122
+ "attempt-of",
3123
+ "retry-of",
3124
+ "fallback-of",
3125
+ "remediation-of",
3126
+ "evidence-for",
3127
+ "accepted-by",
3128
+ "supersedes"
3129
+ ].includes(unsupported)) {
3130
+ diagnostics.push({
3131
+ code: "AI_RELATIONSHIP_UNSUPPORTED_TYPE",
3132
+ message: `Unsupported relationshipType ${unsupported} was reported without flattening.`,
3133
+ eventId: event.eventId
3062
3134
  });
3063
- continue;
3064
3135
  }
3065
- diagnostics.push({
3066
- code: "AI_LOGICAL_PARENT_REMAPPED",
3067
- message: `Remapped parent ${originalParentId} \u2192 ${nextParent} for ${event.eventId}.`,
3068
- eventIds: [event.eventId]
3069
- });
3070
- normalized.push({
3071
- ...event,
3072
- parentId: nextParent,
3073
- projection: {
3074
- ...event.projection,
3075
- parentNormalized: true,
3076
- originalParentId
3077
- }
3078
- });
3079
3136
  }
3080
- const rawIndex = new Map(events.map((e, i) => [e.eventId, i]));
3081
- normalized.sort((a, b) => {
3082
- const ai = rawIndex.get(a.sourceEventIds[0]) ?? 0;
3083
- const bi = rawIndex.get(b.sourceEventIds[0]) ?? 0;
3084
- return ai - bi || a.eventId.localeCompare(b.eventId);
3085
- });
3086
3137
  return {
3087
- logicalEvents: Object.freeze(normalized),
3138
+ relationships: Object.freeze(relationships),
3088
3139
  diagnostics: Object.freeze(diagnostics)
3089
3140
  };
3090
3141
  }
3091
- function resolveCanonicalToolName(event) {
3092
- const attrs2 = event.attributes;
3093
- const direct = pickString(attrs2, ["toolName", "tool"]);
3094
- if (direct) return direct;
3095
- const metadata = attrs2?.metadata;
3096
- if (isRecord6(metadata)) {
3097
- const nested = pickString(metadata, ["toolName", "tool"]);
3098
- if (nested) return nested;
3099
- }
3100
- for (const prefix of ["tool:", "function:", "mcp-tools:"]) {
3101
- if (event.name.startsWith(prefix)) return event.name.slice(prefix.length);
3102
- }
3103
- return event.name;
3104
- }
3105
- function pickString(record, keys) {
3106
- if (!record) return void 0;
3107
- for (const key of keys) {
3108
- const value = record[key];
3109
- if (typeof value === "string" && value.trim() !== "") return value.trim();
3110
- }
3111
- return void 0;
3112
- }
3113
3142
 
3114
- // packages/core/src/checks/derived-failure.ts
3115
- function isRecord7(value) {
3116
- return typeof value === "object" && value !== null && !Array.isArray(value);
3117
- }
3118
- function pickString2(record, keys) {
3119
- if (!record) return void 0;
3120
- for (const key of keys) {
3121
- const value = record[key];
3122
- if (typeof value === "string" && value.trim() !== "") return value;
3123
- }
3124
- return void 0;
3143
+ // packages/core/src/checks/trace-facts.ts
3144
+ function summarizeSemanticParity(events) {
3145
+ const projection = projectLogicalEvents(events);
3146
+ const logical = projection.logicalEvents;
3147
+ const finishedTools = logical.filter(
3148
+ (event) => event.kind === "TOOL" && event.status !== "running"
3149
+ );
3150
+ const finishedToolNames = Object.freeze(
3151
+ finishedTools.map((event) => resolveCanonicalToolName(event)).sort((a, b) => a.localeCompare(b))
3152
+ );
3153
+ const derived = deriveFailureFacts(logical);
3154
+ return {
3155
+ rawEventCount: events.length,
3156
+ logicalEventCount: logical.length,
3157
+ runningLogicalCount: logical.filter((event) => event.status === "running").length,
3158
+ finishedToolNames,
3159
+ finishedToolCount: finishedTools.length,
3160
+ pairedCount: logical.filter((event) => event.projection.paired).length,
3161
+ parentRemapCount: projection.diagnostics.filter(
3162
+ (item) => item.code === "AI_LOGICAL_PARENT_REMAPPED"
3163
+ ).length,
3164
+ diagnostics: projection.diagnostics,
3165
+ failureRoleCounts: derived.failureRoleCounts
3166
+ };
3125
3167
  }
3126
- function pickNumber(record, keys) {
3127
- if (!record) return void 0;
3128
- for (const key of keys) {
3129
- const value = record[key];
3130
- if (typeof value === "number" && Number.isFinite(value) && value > 0) {
3131
- return value;
3132
- }
3168
+ var TRACE_FACTS_INPUT_NOT_NORMALIZED = formatProgrammaticDiagnostic(
3169
+ "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED"
3170
+ );
3171
+ function isTraceReadResult(input) {
3172
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
3173
+ return false;
3133
3174
  }
3134
- return void 0;
3175
+ const record = input;
3176
+ return Array.isArray(record.events) && Array.isArray(record.runs) && typeof record.format === "string" && Array.isArray(record.warnings);
3135
3177
  }
3136
- function eventMetadata(event) {
3137
- const attrs2 = isRecord7(event.attributes) ? event.attributes : void 0;
3138
- const nested = attrs2 !== void 0 && isRecord7(attrs2.metadata) ? attrs2.metadata : void 0;
3139
- return {
3140
- ...attrs2 ?? {},
3141
- ...nested ?? {}
3142
- };
3178
+ function looksLikeRawV01TraceEvents(input) {
3179
+ if (!Array.isArray(input) || input.length === 0) return false;
3180
+ const first = input[0];
3181
+ if (typeof first !== "object" || first === null) return false;
3182
+ const row = first;
3183
+ return typeof row.event === "string" && (row.schemaVersion === "0.1" || row.eventId === void 0);
3143
3184
  }
3144
- function canonicalName(event) {
3145
- if (event.kind === "TOOL") return resolveCanonicalToolName(event);
3146
- return event.name;
3185
+ function isPersistedInspectEventArray(input) {
3186
+ if (!Array.isArray(input)) return false;
3187
+ if (input.length === 0) return true;
3188
+ const first = input[0];
3189
+ if (typeof first !== "object" || first === null) return false;
3190
+ const row = first;
3191
+ return typeof row.eventId === "string" && (row.schemaVersion === "0.2" || row.schemaVersion === "1.0" || row.schemaVersion === "0.1") && typeof row.event !== "string";
3147
3192
  }
3148
- function linkKeys(event) {
3149
- const meta = eventMetadata(event);
3150
- const keys = [];
3151
- for (const key of ["linkedStepId", "toolCallId", "mcpToolCallId"]) {
3152
- const value = pickString2(meta, [key]);
3153
- if (value !== void 0) keys.push(`${key}:${value}`);
3193
+ function resolveTraceFactsEvents(input) {
3194
+ if (isTraceReadResult(input)) {
3195
+ return input.events;
3154
3196
  }
3155
- const stepId = pickString2(meta, ["stepId"]);
3156
- if (stepId !== void 0) keys.push(`stepId:${stepId}`);
3157
- return keys;
3197
+ if (looksLikeRawV01TraceEvents(input)) {
3198
+ throw new TypeError(TRACE_FACTS_INPUT_NOT_NORMALIZED);
3199
+ }
3200
+ if (isPersistedInspectEventArray(input)) {
3201
+ return input;
3202
+ }
3203
+ throw new TypeError(TRACE_FACTS_INPUT_NOT_NORMALIZED);
3158
3204
  }
3159
- function buildRunContexts(logicalEvents) {
3160
- const byRun = /* @__PURE__ */ new Map();
3161
- for (const event of logicalEvents) {
3162
- const existing = byRun.get(event.runId) ?? {
3163
- runId: event.runId,
3164
- name: event.name
3165
- };
3166
- const meta = eventMetadata(event);
3167
- if (event.kind === "RUN" || existing.name === event.runId) {
3168
- existing.name = event.name || existing.name;
3205
+ function buildTraceFacts(input) {
3206
+ const events = resolveTraceFactsEvents(input);
3207
+ const projection = projectLogicalEvents(events);
3208
+ const toolsByName = /* @__PURE__ */ new Map();
3209
+ const llmEvents = [];
3210
+ const outcomeEvents = [];
3211
+ for (const event of projection.logicalEvents) {
3212
+ if (event.kind === "TOOL" && event.status !== "running") {
3213
+ const name = resolveCanonicalToolName(event);
3214
+ const list = toolsByName.get(name) ?? [];
3215
+ list.push(event);
3216
+ toolsByName.set(name, list);
3169
3217
  }
3170
- if (event.kind === "RUN" && event.status !== void 0 && event.status !== "running") {
3171
- existing.status = event.status;
3218
+ if (event.kind === "LLM" && event.status !== "running") {
3219
+ llmEvents.push(event);
3220
+ }
3221
+ if (event.kind === "OUTCOME") {
3222
+ outcomeEvents.push(event);
3172
3223
  }
3173
- existing.retryOf ??= pickString2(meta, ["retryOf"]);
3174
- existing.attempt ??= pickNumber(meta, ["attempt", "retryAttempt", "retryCount"]);
3175
- existing.sessionId ??= pickString2(meta, ["sessionId", "conversationId"]);
3176
- existing.groupId ??= pickString2(meta, ["groupId"]);
3177
- existing.parentGroupId ??= pickString2(meta, ["parentGroupId"]);
3178
- existing.fallbackOf ??= pickString2(meta, ["fallbackOf", "fallbackFrom"]);
3179
- byRun.set(event.runId, existing);
3180
3224
  }
3181
- return byRun;
3182
- }
3183
- function sameCorrelationScope(a, b) {
3184
- if (a.sessionId && b.sessionId && a.sessionId === b.sessionId) return true;
3185
- if (a.groupId && b.groupId && a.groupId === b.groupId) return true;
3186
- if (a.parentGroupId && b.parentGroupId && a.parentGroupId === b.parentGroupId) {
3187
- return true;
3225
+ for (const [name, list] of [...toolsByName.entries()]) {
3226
+ toolsByName.set(name, Object.freeze([...list]));
3188
3227
  }
3189
- return false;
3190
- }
3191
- function isSuccessful(event) {
3192
- return event.status === "ok";
3193
- }
3194
- function isFailure(event) {
3195
- return event.status === "error";
3196
- }
3197
- function compareEventOrder(a, b) {
3198
- const byTime = a.timestamp.localeCompare(b.timestamp);
3199
- if (byTime !== 0) return byTime;
3200
- return a.eventId.localeCompare(b.eventId);
3228
+ const derived = deriveFailureFacts(projection.logicalEvents);
3229
+ const relationship = deriveRelationshipFacts(events);
3230
+ const summary = summarizeSemanticParity(events);
3231
+ return {
3232
+ rawEvents: Object.freeze([...events]),
3233
+ logicalEvents: projection.logicalEvents,
3234
+ diagnostics: projection.diagnostics,
3235
+ toolsByName,
3236
+ llmEvents: Object.freeze(llmEvents),
3237
+ outcomeEvents: Object.freeze(outcomeEvents),
3238
+ summary: {
3239
+ ...summary,
3240
+ failureRoleCounts: derived.failureRoleCounts
3241
+ },
3242
+ failureFacts: derived.failureFacts,
3243
+ failuresByRole: derived.failuresByRole,
3244
+ relationships: relationship.relationships,
3245
+ relationshipDiagnostics: relationship.diagnostics
3246
+ };
3201
3247
  }
3202
- function collectCandidates(failure, logicalEvents, runs) {
3203
- const failureRun = runs.get(failure.runId);
3204
- const failureLinks = new Set(linkKeys(failure));
3205
- const failureName = canonicalName(failure);
3206
- const failureAttempt = pickNumber(eventMetadata(failure), ["attempt", "retryAttempt", "retryCount"]) ?? failureRun?.attempt;
3207
- const candidates = [];
3208
- for (const event of logicalEvents) {
3209
- if (event.eventId === failure.eventId) continue;
3210
- if (event.status === "running") continue;
3211
- const eventRun = runs.get(event.runId);
3212
- const eventMeta = eventMetadata(event);
3213
- const sameRun = event.runId === failure.runId;
3214
- if (eventRun?.retryOf === failure.runId) {
3215
- candidates.push({
3216
- event,
3217
- basis: "retryOf",
3218
- confidence: "explicit",
3219
- viaRunId: event.runId
3220
- });
3221
- continue;
3222
- }
3223
- if (eventRun?.fallbackOf === failure.runId || pickString2(eventMeta, ["fallbackOf", "fallbackFrom"]) === failure.runId) {
3224
- candidates.push({
3225
- event,
3226
- basis: "fallbackOf",
3227
- confidence: "explicit",
3228
- viaRunId: event.runId
3229
- });
3230
- continue;
3231
- }
3232
- if (sameRun && compareEventOrder(failure, event) >= 0) continue;
3233
- const eventLinks = linkKeys(event);
3234
- const sharedLink = eventLinks.find((key) => failureLinks.has(key));
3235
- if (sharedLink !== void 0) {
3236
- candidates.push({
3237
- event,
3238
- basis: sharedLink.split(":")[0] ?? "linkedId",
3239
- confidence: "explicit"
3240
- });
3241
- continue;
3242
- }
3243
- const eventAttempt = pickNumber(eventMeta, ["attempt", "retryAttempt", "retryCount"]) ?? eventRun?.attempt;
3244
- const sameParent = failure.parentId !== void 0 && event.parentId !== void 0 && failure.parentId === event.parentId;
3245
- const differentParent = failure.parentId !== void 0 && event.parentId !== void 0 && failure.parentId !== event.parentId;
3246
- const sessionScoped = failureRun !== void 0 && eventRun !== void 0 && sameCorrelationScope(failureRun, eventRun);
3247
- if (canonicalName(event) === failureName && failureAttempt !== void 0 && eventAttempt !== void 0 && eventAttempt > failureAttempt && !differentParent && (sameParent || sessionScoped || sameRun)) {
3248
- candidates.push({
3249
- event,
3250
- basis: "attempt-progression",
3251
- confidence: "correlated",
3252
- ...event.runId !== failure.runId ? { viaRunId: event.runId } : {}
3253
- });
3254
- }
3248
+
3249
+ // packages/core/src/checks/index.ts
3250
+ var SEVERITY_RANK = {
3251
+ error: 0,
3252
+ warning: 1,
3253
+ info: 2
3254
+ };
3255
+ var STATUS_RANK = {
3256
+ fail: 0,
3257
+ warning: 1,
3258
+ pass: 2
3259
+ };
3260
+ var DEFAULT_SENSITIVE_KEYS = DEFAULT_CREDENTIAL_SENSITIVE_KEYS;
3261
+ var DEFAULT_RAW_CONTENT_KEYS = [
3262
+ "body",
3263
+ "headers",
3264
+ "input",
3265
+ "messages",
3266
+ "output",
3267
+ "payload",
3268
+ "prompt",
3269
+ "requestbody",
3270
+ "request_body",
3271
+ "responsebody",
3272
+ "response_body",
3273
+ "rawprompt",
3274
+ "raw_prompt",
3275
+ "rawoutput",
3276
+ "raw_output",
3277
+ "toolinput",
3278
+ "tool_input",
3279
+ "tooloutput",
3280
+ "tool_output",
3281
+ // Framework / agent metadata that carries user or task text
3282
+ "currenttask",
3283
+ "current_task",
3284
+ "task",
3285
+ "userinput",
3286
+ "user_input",
3287
+ "requesttext",
3288
+ "request_text",
3289
+ "conversationtext",
3290
+ "conversation_text"
3291
+ ];
3292
+ var DEFAULT_SAFE_RAW_CONTENT_PATH_PREFIXES = [
3293
+ "tokenUsage",
3294
+ "usage",
3295
+ "tokens"
3296
+ ];
3297
+ var SAFE_USAGE_LEAF_KEYS = /* @__PURE__ */ new Set([
3298
+ "input",
3299
+ "output",
3300
+ "total",
3301
+ "cached",
3302
+ "cachewrite",
3303
+ "cache_write",
3304
+ "reasoning",
3305
+ "input_tokens",
3306
+ "inputtokens",
3307
+ "output_tokens",
3308
+ "outputtokens",
3309
+ "total_tokens",
3310
+ "totaltokens",
3311
+ "prompt_tokens",
3312
+ "prompttokens",
3313
+ "completion_tokens",
3314
+ "completiontokens",
3315
+ "cache_read",
3316
+ "cacheread",
3317
+ "cache_creation",
3318
+ "cachecreation",
3319
+ "reasoning_tokens",
3320
+ "reasoningtokens"
3321
+ ]);
3322
+ var DEFAULT_SECRET_PATTERNS = [
3323
+ { id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
3324
+ { id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
3325
+ { id: "aws-access-key", pattern: /AKIA[0-9A-Z]{16}/ },
3326
+ { id: "github-token", pattern: /gh[opsu]_[A-Za-z0-9_]{20,}/ },
3327
+ // Keep in sync with packages/redact/src/key-value-secret.ts (KEY_VALUE_SECRET_PATTERN_SOURCE).
3328
+ {
3329
+ id: "key-value-secret",
3330
+ pattern: /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})/i
3255
3331
  }
3256
- const byId = /* @__PURE__ */ new Map();
3257
- for (const candidate of candidates) {
3258
- const prev = byId.get(candidate.event.eventId);
3259
- if (!prev || prev.confidence !== "explicit" && candidate.confidence === "explicit") {
3260
- byId.set(candidate.event.eventId, candidate);
3332
+ ];
3333
+ function compareStrings(a, b) {
3334
+ return (a ?? "").localeCompare(b ?? "");
3335
+ }
3336
+ function diagnostic(code, message, ruleId) {
3337
+ return {
3338
+ code,
3339
+ message,
3340
+ severity: "error",
3341
+ ...ruleId ? { ruleId } : {}
3342
+ };
3343
+ }
3344
+ function emptySummary() {
3345
+ return {
3346
+ passed: 0,
3347
+ failed: 0,
3348
+ warnings: 0,
3349
+ errors: 0,
3350
+ rulesEvaluated: 0
3351
+ };
3352
+ }
3353
+ function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
3354
+ return {
3355
+ ok: false,
3356
+ status: "error",
3357
+ format: input.read.format,
3358
+ ...selectedRun ? { runId: selectedRun.runId } : {},
3359
+ summary: {
3360
+ ...emptySummary(),
3361
+ errors: diagnostics.filter((item) => item.severity === "error").length,
3362
+ rulesEvaluated: ruleExecutions.length
3363
+ },
3364
+ findings: [],
3365
+ diagnostics: [...diagnostics],
3366
+ ruleExecutions: [...ruleExecutions]
3367
+ };
3368
+ }
3369
+ function flattenNodes(nodes) {
3370
+ return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
3371
+ }
3372
+ function buildFacts(input, selectedRun) {
3373
+ const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
3374
+ const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
3375
+ const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
3376
+ const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
3377
+ const nodesByEventId = /* @__PURE__ */ new Map();
3378
+ const childrenByParentId = /* @__PURE__ */ new Map();
3379
+ for (const node of nodes) {
3380
+ nodesByEventId.set(node.event.eventId, node);
3381
+ const parentId = node.event.parentId;
3382
+ if (parentId) {
3383
+ const children = childrenByParentId.get(parentId) ?? [];
3384
+ children.push(node);
3385
+ childrenByParentId.set(parentId, children);
3261
3386
  }
3262
3387
  }
3263
- return [...byId.values()].sort((a, b) => compareEventOrder(a.event, b.event));
3388
+ const projection = projectLogicalEvents(scopedEvents);
3389
+ return {
3390
+ format: input.read.format,
3391
+ runs: Object.freeze([...input.read.runs]),
3392
+ events: Object.freeze([...scopedEvents]),
3393
+ logicalEvents: projection.logicalEvents,
3394
+ logicalProjectionDiagnostics: projection.diagnostics,
3395
+ readerWarnings: Object.freeze([...input.read.warnings]),
3396
+ unsupportedFields: Object.freeze([...input.read.unsupportedFields]),
3397
+ sourceFiles: Object.freeze([...input.read.sourceFiles]),
3398
+ nodesByEventId,
3399
+ childrenByParentId,
3400
+ rootNodes: Object.freeze(scopedRuns.flatMap((run) => run.children))
3401
+ };
3264
3402
  }
3265
- function classifyFailure(failure, candidates, runs, logicalEvents) {
3266
- const successful = candidates.filter((c) => isSuccessful(c.event));
3267
- const unsuccessful = candidates.filter((c) => !isSuccessful(c.event));
3268
- const retryRunIds = Object.freeze(
3269
- [...new Set(candidates.map((c) => c.viaRunId).filter((id) => id !== void 0))].sort(
3270
- (a, b) => a.localeCompare(b)
3271
- )
3272
- );
3273
- if (successful.length > 1) {
3274
- const distinctRuns = new Set(successful.map((c) => c.event.runId));
3275
- const distinctParents = new Set(
3276
- successful.map((c) => c.event.parentId ?? "").filter((id) => id !== "")
3277
- );
3278
- if (distinctRuns.size > 1 || distinctParents.size > 1) {
3403
+ function resolveSelectedRun(input, runId) {
3404
+ if (input.selectedRun) {
3405
+ if (runId && input.selectedRun.runId !== runId) {
3279
3406
  return {
3280
- eventId: failure.eventId,
3281
- runId: failure.runId,
3282
- name: failure.name,
3283
- kind: failure.kind,
3284
- role: "unknown",
3285
- confidence: "unknown",
3286
- basis: Object.freeze(["ambiguous-recovery-candidates"]),
3287
- recoveryEventIds: Object.freeze(
3288
- successful.map((c) => c.event.eventId).sort((a, b) => a.localeCompare(b))
3289
- ),
3290
- retryRunIds
3407
+ diagnostics: [
3408
+ diagnostic(
3409
+ "AI_CHECK_INVALID_ARGUMENTS",
3410
+ `Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
3411
+ )
3412
+ ]
3291
3413
  };
3292
3414
  }
3415
+ return { run: input.selectedRun, diagnostics: [] };
3293
3416
  }
3294
- if (successful.length >= 1) {
3295
- const best = successful[0];
3296
- return {
3297
- eventId: failure.eventId,
3298
- runId: failure.runId,
3299
- name: failure.name,
3300
- kind: failure.kind,
3301
- role: "recovered",
3302
- confidence: best.confidence,
3303
- basis: Object.freeze([best.basis]),
3304
- recoveryEventIds: Object.freeze(
3305
- successful.map((c) => c.event.eventId).sort((a, b) => a.localeCompare(b))
3306
- ),
3307
- retryRunIds
3308
- };
3417
+ if (runId) {
3418
+ const run = input.read.runs.find((candidate) => candidate.runId === runId);
3419
+ if (!run) {
3420
+ return {
3421
+ diagnostics: [
3422
+ diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
3423
+ ]
3424
+ };
3425
+ }
3426
+ return { run, diagnostics: [] };
3309
3427
  }
3310
- if (candidates.length > 0) {
3311
- const best = candidates[0];
3312
- return {
3313
- eventId: failure.eventId,
3314
- runId: failure.runId,
3315
- name: failure.name,
3316
- kind: failure.kind,
3317
- role: "transient",
3318
- confidence: best.confidence,
3319
- basis: Object.freeze([
3320
- best.basis,
3321
- unsuccessful.some((c) => c.event.status === void 0) ? "retry-incomplete" : "retry-without-success"
3322
- ]),
3323
- recoveryEventIds: Object.freeze([]),
3324
- retryRunIds
3325
- };
3428
+ if (input.read.runs.length === 1) {
3429
+ return { run: input.read.runs[0], diagnostics: [] };
3326
3430
  }
3327
- const failureMeta = eventMetadata(failure);
3328
- const declaredSuccessor = pickString2(failureMeta, [
3329
- "retriedBy",
3330
- "nextRetryRunId",
3331
- "retryRunId"
3332
- ]);
3333
- if (declaredSuccessor !== void 0 && !runs.has(declaredSuccessor)) {
3431
+ if (input.read.runs.length === 0) {
3334
3432
  return {
3335
- eventId: failure.eventId,
3336
- runId: failure.runId,
3337
- name: failure.name,
3338
- kind: failure.kind,
3339
- role: "transient",
3340
- confidence: "explicit",
3341
- basis: Object.freeze(["retry-declared", "retry-run-missing"]),
3342
- recoveryEventIds: Object.freeze([]),
3343
- retryRunIds: Object.freeze([declaredSuccessor])
3433
+ diagnostics: [
3434
+ diagnostic(
3435
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
3436
+ formatProgrammaticDiagnostic(
3437
+ "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
3438
+ "No runs are available for checks."
3439
+ )
3440
+ )
3441
+ ]
3344
3442
  };
3345
3443
  }
3346
- for (const run of runs.values()) {
3347
- if (run.retryOf === failure.runId) {
3348
- return {
3349
- eventId: failure.eventId,
3350
- runId: failure.runId,
3351
- name: failure.name,
3352
- kind: failure.kind,
3353
- role: "transient",
3354
- confidence: "explicit",
3355
- basis: Object.freeze(["retryOf", "retry-run-missing-or-empty"]),
3356
- recoveryEventIds: Object.freeze([]),
3357
- retryRunIds: Object.freeze([run.runId])
3358
- };
3444
+ return {
3445
+ diagnostics: [
3446
+ diagnostic(
3447
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
3448
+ formatProgrammaticDiagnostic("AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED")
3449
+ )
3450
+ ]
3451
+ };
3452
+ }
3453
+ function selectRules(rules, selectedIds) {
3454
+ const diagnostics = [];
3455
+ const byId = /* @__PURE__ */ new Map();
3456
+ for (const rule of rules) {
3457
+ if (byId.has(rule.id)) {
3458
+ diagnostics.push(
3459
+ diagnostic("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
3460
+ );
3461
+ continue;
3359
3462
  }
3463
+ byId.set(rule.id, rule);
3360
3464
  }
3361
- const failureRun = runs.get(failure.runId);
3362
- const hasSuccessorDeclared = [...runs.values()].some((run) => run.retryOf === failure.runId);
3363
- const isFinalInChain = failureRun !== void 0 && !hasSuccessorDeclared && (failureRun.retryOf !== void 0 || failureRun.attempt !== void 0 && failureRun.attempt > 1 || pickNumber(eventMetadata(failure), ["attempt"]) !== void 0);
3364
- if (isFinalInChain && failureRun?.status === "error" && !logicalEvents.some(
3365
- (event) => event.runId === failure.runId && event.eventId !== failure.eventId && isSuccessful(event) && canonicalName(event) === canonicalName(failure)
3366
- )) {
3465
+ if (selectedIds && selectedIds.length > 0) {
3466
+ const selected = new Set(selectedIds);
3467
+ for (const id of selected) {
3468
+ if (!byId.has(id)) {
3469
+ diagnostics.push(
3470
+ diagnostic("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
3471
+ );
3472
+ }
3473
+ }
3367
3474
  return {
3368
- eventId: failure.eventId,
3369
- runId: failure.runId,
3370
- name: failure.name,
3371
- kind: failure.kind,
3372
- role: "terminal",
3373
- confidence: failureRun.retryOf !== void 0 ? "explicit" : "correlated",
3374
- basis: Object.freeze(["final-retry-chain-member", "enclosing-run-error"]),
3375
- recoveryEventIds: Object.freeze([]),
3376
- retryRunIds: Object.freeze(
3377
- failureRun.retryOf !== void 0 ? [failureRun.retryOf] : []
3378
- )
3475
+ rules: [...byId.values()].filter((rule) => selected.has(rule.id)).sort(compareRules),
3476
+ diagnostics
3379
3477
  };
3380
3478
  }
3479
+ return { rules: [...byId.values()].sort(compareRules), diagnostics };
3480
+ }
3481
+ function compareRules(a, b) {
3482
+ return a.id.localeCompare(b.id);
3483
+ }
3484
+ function eventTimestamp(finding, eventById) {
3485
+ const eventId = finding.evidence[0]?.eventId;
3486
+ return eventId ? eventById.get(eventId)?.timestamp ?? "" : "";
3487
+ }
3488
+ function compareFindings(eventById) {
3489
+ return (a, b) => {
3490
+ if (SEVERITY_RANK[a.severity] !== SEVERITY_RANK[b.severity]) {
3491
+ return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
3492
+ }
3493
+ const byRule = a.ruleId.localeCompare(b.ruleId);
3494
+ if (byRule !== 0) return byRule;
3495
+ if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
3496
+ return STATUS_RANK[a.status] - STATUS_RANK[b.status];
3497
+ }
3498
+ const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
3499
+ if (byRun !== 0) return byRun;
3500
+ const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
3501
+ if (byTime !== 0) return byTime;
3502
+ const byEvent = compareStrings(a.evidence[0]?.eventId, b.evidence[0]?.eventId);
3503
+ if (byEvent !== 0) return byEvent;
3504
+ return compareStrings(a.evidence[0]?.path, b.evidence[0]?.path);
3505
+ };
3506
+ }
3507
+ function normalizeFinding(rule, finding) {
3381
3508
  return {
3382
- eventId: failure.eventId,
3383
- runId: failure.runId,
3384
- name: failure.name,
3385
- kind: failure.kind,
3386
- role: "unknown",
3387
- confidence: "unknown",
3388
- basis: Object.freeze(["no-explicit-or-correlated-recovery"]),
3389
- recoveryEventIds: Object.freeze([]),
3390
- retryRunIds: Object.freeze([])
3509
+ ruleId: finding.ruleId || rule.id,
3510
+ severity: finding.severity ?? rule.defaultSeverity,
3511
+ status: finding.status,
3512
+ message: finding.message,
3513
+ ...finding.expected !== void 0 ? { expected: finding.expected } : {},
3514
+ ...finding.actual !== void 0 ? { actual: finding.actual } : {},
3515
+ evidence: [...finding.evidence ?? []],
3516
+ ...finding.category !== void 0 ? { category: finding.category } : {},
3517
+ ...finding.confidence !== void 0 ? { confidence: finding.confidence } : {},
3518
+ ...finding.detector !== void 0 ? { detector: finding.detector } : {},
3519
+ ...finding.action !== void 0 ? { action: finding.action } : {}
3391
3520
  };
3392
3521
  }
3393
- function deriveFailureFacts(logicalEvents) {
3394
- const runs = buildRunContexts(logicalEvents);
3395
- const failures = logicalEvents.filter((event) => isFailure(event)).sort(compareEventOrder);
3396
- const failureFacts = failures.map(
3397
- (failure) => classifyFailure(failure, collectCandidates(failure, logicalEvents, runs), runs, logicalEvents)
3398
- );
3399
- const byRole = /* @__PURE__ */ new Map([
3400
- ["transient", []],
3401
- ["recovered", []],
3402
- ["terminal", []],
3403
- ["unknown", []]
3404
- ]);
3405
- for (const fact of failureFacts) {
3406
- byRole.get(fact.role).push(fact);
3522
+ function summarize(findings, diagnostics, rulesEvaluated) {
3523
+ return {
3524
+ passed: findings.filter((finding) => finding.status === "pass").length,
3525
+ failed: findings.filter(
3526
+ (finding) => finding.status === "fail" && finding.severity === "error"
3527
+ ).length,
3528
+ warnings: findings.filter(
3529
+ (finding) => finding.status === "warning" || finding.severity === "warning"
3530
+ ).length,
3531
+ errors: diagnostics.filter((item) => item.severity === "error").length,
3532
+ rulesEvaluated
3533
+ };
3534
+ }
3535
+ function classifyRuleExecution(findings, threw) {
3536
+ if (findings.some((finding) => finding.status === "fail" && finding.severity === "error")) {
3537
+ return "fail";
3407
3538
  }
3408
- for (const [role, list] of byRole) {
3409
- byRole.set(
3410
- role,
3411
- Object.freeze(
3412
- [...list].sort((a, b) => {
3413
- const byRun = a.runId.localeCompare(b.runId);
3414
- if (byRun !== 0) return byRun;
3415
- return a.eventId.localeCompare(b.eventId);
3416
- })
3417
- )
3418
- );
3539
+ if (findings.some(
3540
+ (finding) => finding.status === "warning" || finding.severity === "warning"
3541
+ )) {
3542
+ return "warning";
3419
3543
  }
3420
- const failureRoleCounts = {
3421
- transient: byRole.get("transient").length,
3422
- recovered: byRole.get("recovered").length,
3423
- terminal: byRole.get("terminal").length,
3424
- unknown: byRole.get("unknown").length
3544
+ return "pass";
3545
+ }
3546
+ function eventEvidence(event, path16) {
3547
+ return {
3548
+ runId: event.runId,
3549
+ eventId: event.eventId,
3550
+ parentId: event.parentId,
3551
+ traceId: event.trace?.traceId,
3552
+ spanId: event.trace?.spanId,
3553
+ kind: event.kind,
3554
+ name: event.name,
3555
+ status: event.status,
3556
+ ...path16 ? { path: path16 } : {}
3425
3557
  };
3558
+ }
3559
+ function runEvidence(run) {
3560
+ return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
3561
+ }
3562
+ function failFinding(ruleId, message, evidence, expected, actual, meta) {
3426
3563
  return {
3427
- failureFacts: Object.freeze(failureFacts),
3428
- failuresByRole: byRole,
3429
- failureRoleCounts
3564
+ ruleId,
3565
+ severity: meta?.severity ?? "error",
3566
+ status: meta?.status ?? "fail",
3567
+ message,
3568
+ ...expected !== void 0 ? { expected } : {},
3569
+ ...actual !== void 0 ? { actual } : {},
3570
+ evidence: [...evidence],
3571
+ ...meta?.category !== void 0 ? { category: meta.category } : {},
3572
+ ...meta?.confidence !== void 0 ? { confidence: meta.confidence } : {},
3573
+ ...meta?.detector !== void 0 ? { detector: meta.detector } : {},
3574
+ ...meta?.action !== void 0 ? { action: meta.action } : {}
3430
3575
  };
3431
3576
  }
3432
-
3433
- // packages/core/src/checks/relationship-facts.ts
3434
- function attrs(event) {
3435
- return event.attributes && typeof event.attributes === "object" ? event.attributes : {};
3577
+ function semanticEvents(context) {
3578
+ return context.logicalEvents ?? context.events;
3436
3579
  }
3437
- function stringAttr(record, key) {
3438
- const value = record[key];
3439
- return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
3580
+ function isRecord8(value) {
3581
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3440
3582
  }
3441
- function deriveRelationshipFacts(events) {
3442
- const relationships = [];
3443
- const diagnostics = [];
3444
- const byId = new Map(events.map((event) => [event.eventId, event]));
3445
- const seen = /* @__PURE__ */ new Set();
3446
- const push = (edge) => {
3447
- const key = `${edge.type}:${edge.fromEventId}:${edge.toEventId ?? ""}:${edge.externalRef ?? ""}`;
3448
- if (seen.has(key)) return;
3449
- seen.add(key);
3450
- relationships.push(edge);
3451
- };
3452
- for (const event of events) {
3453
- if (event.parentId) {
3454
- push({
3455
- type: "parent-child",
3456
- fromEventId: event.parentId,
3457
- toEventId: event.eventId,
3458
- confidence: byId.has(event.parentId) ? "explicit" : "unknown",
3459
- basis: ["persisted.parentId"]
3460
- });
3461
- if (!byId.has(event.parentId)) {
3462
- diagnostics.push({
3463
- code: "AI_RELATIONSHIP_PARENT_MISSING",
3464
- message: `parentId ${event.parentId} is not present in the event set.`,
3465
- eventId: event.eventId
3466
- });
3467
- }
3468
- }
3469
- const bag = attrs(event);
3470
- const meta = extractSessionWorkflowMetadata(bag) ?? {};
3471
- const nested = bag.metadata && typeof bag.metadata === "object" ? extractSessionWorkflowMetadata(bag.metadata) : void 0;
3472
- const workflow = { ...meta, ...nested };
3473
- if (workflow.retryOf) {
3474
- const target = events.find((candidate) => candidate.runId === workflow.retryOf);
3475
- push({
3476
- type: "retry-of",
3477
- fromEventId: event.eventId,
3478
- ...target ? { toEventId: target.eventId } : {},
3479
- externalRef: workflow.retryOf,
3480
- confidence: target ? "explicit" : "correlated",
3481
- basis: ["attributes.retryOf"]
3482
- });
3483
- }
3484
- const attemptOf = stringAttr(bag, "attemptOf") ?? stringAttr(bag, "operationId");
3485
- if (attemptOf && workflow.attempt !== void 0) {
3486
- push({
3487
- type: "attempt-of",
3488
- fromEventId: event.eventId,
3489
- externalRef: attemptOf,
3490
- confidence: "explicit",
3491
- basis: workflow.attempt !== void 0 ? ["attributes.attempt", "attributes.operationId"] : ["attributes.operationId"]
3492
- });
3493
- }
3494
- const fallbackOf = stringAttr(bag, "fallbackOf");
3495
- if (fallbackOf) {
3496
- push({
3497
- type: "fallback-of",
3498
- fromEventId: event.eventId,
3499
- externalRef: fallbackOf,
3500
- confidence: "explicit",
3501
- basis: ["attributes.fallbackOf"]
3502
- });
3503
- }
3504
- const remediationOf = stringAttr(bag, "remediationOf");
3505
- if (remediationOf) {
3506
- push({
3507
- type: "remediation-of",
3508
- fromEventId: event.eventId,
3509
- externalRef: remediationOf,
3510
- confidence: "explicit",
3511
- basis: ["attributes.remediationOf"]
3512
- });
3513
- }
3514
- const evidenceFor = stringAttr(bag, "evidenceFor");
3515
- if (evidenceFor) {
3516
- push({
3517
- type: "evidence-for",
3518
- fromEventId: event.eventId,
3519
- ...byId.has(evidenceFor) ? { toEventId: evidenceFor } : { externalRef: evidenceFor },
3520
- confidence: byId.has(evidenceFor) ? "explicit" : "correlated",
3521
- basis: ["attributes.evidenceFor"]
3522
- });
3523
- }
3524
- const acceptedBy = stringAttr(bag, "acceptedBy");
3525
- if (acceptedBy) {
3526
- push({
3527
- type: "accepted-by",
3528
- fromEventId: event.eventId,
3529
- ...byId.has(acceptedBy) ? { toEventId: acceptedBy } : { externalRef: acceptedBy },
3530
- confidence: byId.has(acceptedBy) ? "explicit" : "correlated",
3531
- basis: ["attributes.acceptedBy"]
3532
- });
3533
- }
3534
- const supersedes = stringAttr(bag, "supersedes");
3535
- if (supersedes) {
3536
- push({
3537
- type: "supersedes",
3538
- fromEventId: event.eventId,
3539
- ...byId.has(supersedes) ? { toEventId: supersedes } : { externalRef: supersedes },
3540
- confidence: byId.has(supersedes) ? "explicit" : "correlated",
3541
- basis: ["attributes.supersedes"]
3542
- });
3543
- }
3544
- const sourceLineage = stringAttr(bag, "sourceLineage") ?? stringAttr(bag, "sourceEventId");
3545
- if (sourceLineage) {
3546
- push({
3547
- type: "source-lineage",
3548
- fromEventId: event.eventId,
3549
- externalRef: sourceLineage,
3550
- confidence: "explicit",
3551
- basis: ["attributes.sourceLineage"]
3552
- });
3553
- }
3554
- const unsupported = stringAttr(bag, "relationshipType");
3555
- if (unsupported && unsupported !== "parent-child" && ![
3556
- "source-lineage",
3557
- "attempt-of",
3558
- "retry-of",
3559
- "fallback-of",
3560
- "remediation-of",
3561
- "evidence-for",
3562
- "accepted-by",
3563
- "supersedes"
3564
- ].includes(unsupported)) {
3565
- diagnostics.push({
3566
- code: "AI_RELATIONSHIP_UNSUPPORTED_TYPE",
3567
- message: `Unsupported relationshipType ${unsupported} was reported without flattening.`,
3568
- eventId: event.eventId
3569
- });
3570
- }
3571
- }
3572
- return {
3573
- relationships: Object.freeze(relationships),
3574
- diagnostics: Object.freeze(diagnostics)
3575
- };
3576
- }
3577
-
3578
- // packages/core/src/checks/trace-facts.ts
3579
- function summarizeSemanticParity(events) {
3580
- const projection = projectLogicalEvents(events);
3581
- const logical = projection.logicalEvents;
3582
- const finishedTools = logical.filter(
3583
- (event) => event.kind === "TOOL" && event.status !== "running"
3584
- );
3585
- const finishedToolNames = Object.freeze(
3586
- finishedTools.map((event) => resolveCanonicalToolName(event)).sort((a, b) => a.localeCompare(b))
3587
- );
3588
- const derived = deriveFailureFacts(logical);
3589
- return {
3590
- rawEventCount: events.length,
3591
- logicalEventCount: logical.length,
3592
- runningLogicalCount: logical.filter((event) => event.status === "running").length,
3593
- finishedToolNames,
3594
- finishedToolCount: finishedTools.length,
3595
- pairedCount: logical.filter((event) => event.projection.paired).length,
3596
- parentRemapCount: projection.diagnostics.filter(
3597
- (item) => item.code === "AI_LOGICAL_PARENT_REMAPPED"
3598
- ).length,
3599
- diagnostics: projection.diagnostics,
3600
- failureRoleCounts: derived.failureRoleCounts
3601
- };
3602
- }
3603
- var TRACE_FACTS_INPUT_NOT_NORMALIZED = formatProgrammaticDiagnostic(
3604
- "AI_TRACE_FACTS_INPUT_NOT_NORMALIZED"
3605
- );
3606
- function isTraceReadResult(input) {
3607
- if (typeof input !== "object" || input === null || Array.isArray(input)) {
3608
- return false;
3609
- }
3610
- const record = input;
3611
- return Array.isArray(record.events) && Array.isArray(record.runs) && typeof record.format === "string" && Array.isArray(record.warnings);
3583
+ function normalizedKey(value) {
3584
+ return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
3612
3585
  }
3613
- function looksLikeRawV01TraceEvents(input) {
3614
- if (!Array.isArray(input) || input.length === 0) return false;
3615
- const first = input[0];
3616
- if (typeof first !== "object" || first === null) return false;
3617
- const row = first;
3618
- return typeof row.event === "string" && (row.schemaVersion === "0.1" || row.eventId === void 0);
3586
+ function lastPathSegment(path16) {
3587
+ const parts = path16.split(".");
3588
+ return parts[parts.length - 1] ?? path16;
3619
3589
  }
3620
- function isPersistedInspectEventArray(input) {
3621
- if (!Array.isArray(input)) return false;
3622
- if (input.length === 0) return true;
3623
- const first = input[0];
3624
- if (typeof first !== "object" || first === null) return false;
3625
- const row = first;
3626
- return typeof row.eventId === "string" && (row.schemaVersion === "0.2" || row.schemaVersion === "1.0" || row.schemaVersion === "0.1") && typeof row.event !== "string";
3590
+ function valueType(value) {
3591
+ if (Array.isArray(value)) return "array";
3592
+ if (value === null) return "null";
3593
+ return typeof value;
3627
3594
  }
3628
- function resolveTraceFactsEvents(input) {
3629
- if (isTraceReadResult(input)) {
3630
- return input.events;
3595
+ function serializedByteLength(value) {
3596
+ try {
3597
+ return Buffer.byteLength(JSON.stringify(value), "utf-8");
3598
+ } catch {
3599
+ return void 0;
3631
3600
  }
3632
- if (looksLikeRawV01TraceEvents(input)) {
3633
- throw new TypeError(TRACE_FACTS_INPUT_NOT_NORMALIZED);
3601
+ }
3602
+ function pushValueEntries(entries, event, value, path16, key, depth = 0) {
3603
+ entries.push({ event, path: path16, key, value });
3604
+ if (depth >= 8) return;
3605
+ if (Array.isArray(value)) {
3606
+ for (const [index, item] of value.entries()) {
3607
+ pushValueEntries(entries, event, item, `${path16}.${index}`, String(index), depth + 1);
3608
+ }
3609
+ return;
3634
3610
  }
3635
- if (isPersistedInspectEventArray(input)) {
3636
- return input;
3611
+ if (!isRecord8(value)) return;
3612
+ for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
3613
+ pushValueEntries(
3614
+ entries,
3615
+ event,
3616
+ value[nestedKey],
3617
+ `${path16}.${nestedKey}`,
3618
+ nestedKey,
3619
+ depth + 1
3620
+ );
3637
3621
  }
3638
- throw new TypeError(TRACE_FACTS_INPUT_NOT_NORMALIZED);
3639
3622
  }
3640
- function buildTraceFacts(input) {
3641
- const events = resolveTraceFactsEvents(input);
3642
- const projection = projectLogicalEvents(events);
3643
- const toolsByName = /* @__PURE__ */ new Map();
3644
- const llmEvents = [];
3645
- const outcomeEvents = [];
3646
- for (const event of projection.logicalEvents) {
3647
- if (event.kind === "TOOL" && event.status !== "running") {
3648
- const name = resolveCanonicalToolName(event);
3649
- const list = toolsByName.get(name) ?? [];
3650
- list.push(event);
3651
- toolsByName.set(name, list);
3652
- }
3653
- if (event.kind === "LLM" && event.status !== "running") {
3654
- llmEvents.push(event);
3623
+ function eventValueEntries(event, options = {}) {
3624
+ const entries = [];
3625
+ if (event.attributes !== void 0) {
3626
+ pushValueEntries(entries, event, event.attributes, "attributes", "attributes");
3627
+ }
3628
+ if (options.includeSummaries) {
3629
+ if (event.inputSummary !== void 0) {
3630
+ pushValueEntries(entries, event, event.inputSummary, "inputSummary", "inputSummary");
3655
3631
  }
3656
- if (event.kind === "OUTCOME") {
3657
- outcomeEvents.push(event);
3632
+ if (event.outputSummary !== void 0) {
3633
+ pushValueEntries(entries, event, event.outputSummary, "outputSummary", "outputSummary");
3658
3634
  }
3659
3635
  }
3660
- for (const [name, list] of [...toolsByName.entries()]) {
3661
- toolsByName.set(name, Object.freeze([...list]));
3636
+ if (options.includeError && event.error !== void 0) {
3637
+ pushValueEntries(entries, event, event.error, "error", "error");
3662
3638
  }
3663
- const derived = deriveFailureFacts(projection.logicalEvents);
3664
- const relationship = deriveRelationshipFacts(events);
3665
- const summary = summarizeSemanticParity(events);
3666
- return {
3667
- rawEvents: Object.freeze([...events]),
3668
- logicalEvents: projection.logicalEvents,
3669
- diagnostics: projection.diagnostics,
3670
- toolsByName,
3671
- llmEvents: Object.freeze(llmEvents),
3672
- outcomeEvents: Object.freeze(outcomeEvents),
3673
- summary: {
3674
- ...summary,
3675
- failureRoleCounts: derived.failureRoleCounts
3676
- },
3677
- failureFacts: derived.failureFacts,
3678
- failuresByRole: derived.failuresByRole,
3679
- relationships: relationship.relationships,
3680
- relationshipDiagnostics: relationship.diagnostics
3681
- };
3639
+ return entries;
3682
3640
  }
3683
-
3684
- // packages/core/src/checks/contract.ts
3685
- new Set(OBSERVED_OUTCOME_METHODS);
3686
-
3687
- // packages/core/src/checks/index.ts
3688
- var SEVERITY_RANK = {
3689
- error: 0,
3690
- warning: 1,
3691
- info: 2
3692
- };
3693
- var STATUS_RANK = {
3694
- fail: 0,
3695
- warning: 1,
3696
- pass: 2
3697
- };
3698
- var DEFAULT_SENSITIVE_KEYS = DEFAULT_CREDENTIAL_SENSITIVE_KEYS;
3699
- var DEFAULT_RAW_CONTENT_KEYS = [
3700
- "body",
3701
- "headers",
3702
- "input",
3703
- "messages",
3704
- "output",
3705
- "payload",
3706
- "prompt",
3707
- "requestbody",
3708
- "request_body",
3709
- "responsebody",
3710
- "response_body",
3711
- "rawprompt",
3712
- "raw_prompt",
3713
- "rawoutput",
3714
- "raw_output",
3715
- "toolinput",
3716
- "tool_input",
3717
- "tooloutput",
3718
- "tool_output",
3719
- // Framework / agent metadata that carries user or task text
3720
- "currenttask",
3721
- "current_task",
3722
- "task",
3723
- "userinput",
3724
- "user_input",
3725
- "requesttext",
3726
- "request_text",
3727
- "conversationtext",
3728
- "conversation_text"
3729
- ];
3730
- var DEFAULT_SAFE_RAW_CONTENT_PATH_PREFIXES = [
3731
- "tokenUsage",
3732
- "usage",
3733
- "tokens"
3734
- ];
3735
- var SAFE_USAGE_LEAF_KEYS = /* @__PURE__ */ new Set([
3736
- "input",
3737
- "output",
3738
- "total",
3739
- "cached",
3740
- "input_tokens",
3741
- "inputtokens",
3742
- "output_tokens",
3743
- "outputtokens",
3744
- "total_tokens",
3745
- "totaltokens",
3746
- "prompt_tokens",
3747
- "prompttokens",
3748
- "completion_tokens",
3749
- "completiontokens"
3750
- ]);
3751
- var DEFAULT_SECRET_PATTERNS = [
3752
- { id: "bearer-token", pattern: /Bearer\s+[A-Za-z0-9._~+/-]{12,}=*/ },
3753
- { id: "openai-key", pattern: /sk-[A-Za-z0-9_-]{16,}/ },
3754
- { id: "aws-access-key", pattern: /AKIA[0-9A-Z]{16}/ },
3755
- { id: "github-token", pattern: /gh[opsu]_[A-Za-z0-9_]{20,}/ },
3756
- // Keep in sync with packages/redact/src/key-value-secret.ts (KEY_VALUE_SECRET_PATTERN_SOURCE).
3757
- {
3758
- id: "key-value-secret",
3759
- pattern: /\b(?:api[_-]?key|internal[_-]?token|access[_-]?token|auth[_-]?token|password|secret|token)=([^\s"'\\]{8,})/i
3760
- }
3761
- ];
3762
- function compareStrings(a, b) {
3763
- return (a ?? "").localeCompare(b ?? "");
3641
+ function limitFindings(findings, maxFindings) {
3642
+ if (maxFindings === void 0 || findings.length <= maxFindings) return findings;
3643
+ return findings.slice(0, Math.max(0, maxFindings));
3764
3644
  }
3765
- function diagnostic(code, message, ruleId) {
3766
- return {
3767
- code,
3768
- message,
3769
- severity: "error",
3770
- ...ruleId ? { ruleId } : {}
3771
- };
3645
+ function hasRedactionMarker(value, markers) {
3646
+ return markers.some((marker) => value.includes(marker)) || /^\[HASH:[A-Za-z0-9_-]+\]$/.test(value);
3772
3647
  }
3773
- function emptySummary() {
3774
- return {
3775
- passed: 0,
3776
- failed: 0,
3777
- warnings: 0,
3778
- errors: 0,
3779
- rulesEvaluated: 0
3780
- };
3648
+ function isSensitiveKey(key, sensitiveKeys) {
3649
+ return isCredentialSensitiveKey(key, sensitiveKeys);
3781
3650
  }
3782
- function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
3783
- return {
3784
- ok: false,
3785
- status: "error",
3786
- format: input.read.format,
3787
- ...selectedRun ? { runId: selectedRun.runId } : {},
3788
- summary: {
3789
- ...emptySummary(),
3790
- errors: diagnostics.filter((item) => item.severity === "error").length,
3791
- rulesEvaluated: ruleExecutions.length
3792
- },
3793
- findings: [],
3794
- diagnostics: [...diagnostics],
3795
- ruleExecutions: [...ruleExecutions]
3796
- };
3651
+ function isRawContentKey(key, forbiddenKeys) {
3652
+ if (!key) return false;
3653
+ const normalized = normalizedKey(key);
3654
+ return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
3797
3655
  }
3798
- function flattenNodes(nodes) {
3799
- return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
3656
+ function isSafeRawContentMetricPath(path16, key, safePathPrefixes) {
3657
+ const leaf = normalizedKey(key ?? lastPathSegment(path16));
3658
+ if (!SAFE_USAGE_LEAF_KEYS.has(leaf)) return false;
3659
+ const parts = path16.split(".").filter(Boolean);
3660
+ if (parts.length < 2) return false;
3661
+ const parent = parts[parts.length - 2] ?? "";
3662
+ const parentNorm = normalizedKey(parent);
3663
+ return safePathPrefixes.some((prefix) => parentNorm === normalizedKey(prefix));
3800
3664
  }
3801
- function buildFacts(input, selectedRun) {
3802
- const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
3803
- const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
3804
- const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
3805
- const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
3806
- const nodesByEventId = /* @__PURE__ */ new Map();
3807
- const childrenByParentId = /* @__PURE__ */ new Map();
3808
- for (const node of nodes) {
3809
- nodesByEventId.set(node.event.eventId, node);
3810
- const parentId = node.event.parentId;
3811
- if (parentId) {
3812
- const children = childrenByParentId.get(parentId) ?? [];
3813
- children.push(node);
3814
- childrenByParentId.set(parentId, children);
3815
- }
3816
- }
3817
- const projection = projectLogicalEvents(scopedEvents);
3818
- return {
3819
- format: input.read.format,
3820
- runs: Object.freeze([...input.read.runs]),
3821
- events: Object.freeze([...scopedEvents]),
3822
- logicalEvents: projection.logicalEvents,
3823
- logicalProjectionDiagnostics: projection.diagnostics,
3824
- readerWarnings: Object.freeze([...input.read.warnings]),
3825
- unsupportedFields: Object.freeze([...input.read.unsupportedFields]),
3826
- sourceFiles: Object.freeze([...input.read.sourceFiles]),
3827
- nodesByEventId,
3828
- childrenByParentId,
3829
- rootNodes: Object.freeze(scopedRuns.flatMap((run) => run.children))
3830
- };
3665
+ function isRawContentPath(path16, key, forbiddenKeys, safePathPrefixes) {
3666
+ if (isSafeRawContentMetricPath(path16, key, safePathPrefixes)) return false;
3667
+ return isRawContentKey(key ?? lastPathSegment(path16), forbiddenKeys);
3831
3668
  }
3832
- function resolveSelectedRun(input, runId) {
3833
- if (input.selectedRun) {
3834
- if (runId && input.selectedRun.runId !== runId) {
3835
- return {
3836
- diagnostics: [
3837
- diagnostic(
3838
- "AI_CHECK_INVALID_ARGUMENTS",
3839
- `Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
3669
+ function createRunStatusRule(options = {}) {
3670
+ const expected = options.expected ?? "ok";
3671
+ const allowIncomplete = options.allowIncomplete === true;
3672
+ return {
3673
+ id: "run.status",
3674
+ category: "run",
3675
+ defaultSeverity: "error",
3676
+ evaluate(context) {
3677
+ const findings = [];
3678
+ const actual = context.selectedRun?.status ?? "unknown";
3679
+ if (actual !== expected) {
3680
+ findings.push(
3681
+ failFinding(
3682
+ "run.status",
3683
+ `Run status ${actual} did not match expected ${expected}.`,
3684
+ runEvidence(context.selectedRun),
3685
+ expected,
3686
+ actual
3840
3687
  )
3841
- ]
3842
- };
3843
- }
3844
- return { run: input.selectedRun, diagnostics: [] };
3845
- }
3846
- if (runId) {
3847
- const run = input.read.runs.find((candidate) => candidate.runId === runId);
3848
- if (!run) {
3849
- return {
3850
- diagnostics: [
3851
- diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
3852
- ]
3853
- };
3854
- }
3855
- return { run, diagnostics: [] };
3856
- }
3857
- if (input.read.runs.length === 1) {
3858
- return { run: input.read.runs[0], diagnostics: [] };
3859
- }
3860
- if (input.read.runs.length === 0) {
3861
- return {
3862
- diagnostics: [
3863
- diagnostic(
3864
- "AI_CHECK_RUN_SELECTION_REQUIRED",
3865
- formatProgrammaticDiagnostic(
3866
- "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
3867
- "No runs are available for checks."
3868
- )
3869
- )
3870
- ]
3871
- };
3872
- }
3873
- return {
3874
- diagnostics: [
3875
- diagnostic(
3876
- "AI_CHECK_RUN_SELECTION_REQUIRED",
3877
- formatProgrammaticDiagnostic("AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED")
3878
- )
3879
- ]
3880
- };
3881
- }
3882
- function selectRules(rules, selectedIds) {
3883
- const diagnostics = [];
3884
- const byId = /* @__PURE__ */ new Map();
3885
- for (const rule of rules) {
3886
- if (byId.has(rule.id)) {
3887
- diagnostics.push(
3888
- diagnostic("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
3889
- );
3890
- continue;
3891
- }
3892
- byId.set(rule.id, rule);
3893
- }
3894
- if (selectedIds && selectedIds.length > 0) {
3895
- const selected = new Set(selectedIds);
3896
- for (const id of selected) {
3897
- if (!byId.has(id)) {
3898
- diagnostics.push(
3899
- diagnostic("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
3900
- );
3901
- }
3902
- }
3903
- return {
3904
- rules: [...byId.values()].filter((rule) => selected.has(rule.id)).sort(compareRules),
3905
- diagnostics
3906
- };
3907
- }
3908
- return { rules: [...byId.values()].sort(compareRules), diagnostics };
3909
- }
3910
- function compareRules(a, b) {
3911
- return a.id.localeCompare(b.id);
3912
- }
3913
- function eventTimestamp(finding, eventById) {
3914
- const eventId = finding.evidence[0]?.eventId;
3915
- return eventId ? eventById.get(eventId)?.timestamp ?? "" : "";
3916
- }
3917
- function compareFindings(eventById) {
3918
- return (a, b) => {
3919
- if (SEVERITY_RANK[a.severity] !== SEVERITY_RANK[b.severity]) {
3920
- return SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
3921
- }
3922
- const byRule = a.ruleId.localeCompare(b.ruleId);
3923
- if (byRule !== 0) return byRule;
3924
- if (STATUS_RANK[a.status] !== STATUS_RANK[b.status]) {
3925
- return STATUS_RANK[a.status] - STATUS_RANK[b.status];
3926
- }
3927
- const byRun = compareStrings(a.evidence[0]?.runId, b.evidence[0]?.runId);
3928
- if (byRun !== 0) return byRun;
3929
- const byTime = eventTimestamp(a, eventById).localeCompare(eventTimestamp(b, eventById));
3930
- if (byTime !== 0) return byTime;
3931
- const byEvent = compareStrings(a.evidence[0]?.eventId, b.evidence[0]?.eventId);
3932
- if (byEvent !== 0) return byEvent;
3933
- return compareStrings(a.evidence[0]?.path, b.evidence[0]?.path);
3934
- };
3935
- }
3936
- function normalizeFinding(rule, finding) {
3937
- return {
3938
- ruleId: finding.ruleId || rule.id,
3939
- severity: finding.severity ?? rule.defaultSeverity,
3940
- status: finding.status,
3941
- message: finding.message,
3942
- ...finding.expected !== void 0 ? { expected: finding.expected } : {},
3943
- ...finding.actual !== void 0 ? { actual: finding.actual } : {},
3944
- evidence: [...finding.evidence ?? []],
3945
- ...finding.category !== void 0 ? { category: finding.category } : {},
3946
- ...finding.confidence !== void 0 ? { confidence: finding.confidence } : {},
3947
- ...finding.detector !== void 0 ? { detector: finding.detector } : {},
3948
- ...finding.action !== void 0 ? { action: finding.action } : {}
3949
- };
3950
- }
3951
- function summarize(findings, diagnostics, rulesEvaluated) {
3952
- return {
3953
- passed: findings.filter((finding) => finding.status === "pass").length,
3954
- failed: findings.filter(
3955
- (finding) => finding.status === "fail" && finding.severity === "error"
3956
- ).length,
3957
- warnings: findings.filter(
3958
- (finding) => finding.status === "warning" || finding.severity === "warning"
3959
- ).length,
3960
- errors: diagnostics.filter((item) => item.severity === "error").length,
3961
- rulesEvaluated
3962
- };
3963
- }
3964
- function classifyRuleExecution(findings, threw) {
3965
- if (findings.some((finding) => finding.status === "fail" && finding.severity === "error")) {
3966
- return "fail";
3967
- }
3968
- if (findings.some(
3969
- (finding) => finding.status === "warning" || finding.severity === "warning"
3970
- )) {
3971
- return "warning";
3972
- }
3973
- return "pass";
3974
- }
3975
- function eventEvidence(event, path16) {
3976
- return {
3977
- runId: event.runId,
3978
- eventId: event.eventId,
3979
- parentId: event.parentId,
3980
- traceId: event.trace?.traceId,
3981
- spanId: event.trace?.spanId,
3982
- kind: event.kind,
3983
- name: event.name,
3984
- status: event.status,
3985
- ...path16 ? { path: path16 } : {}
3986
- };
3987
- }
3988
- function runEvidence(run) {
3989
- return run ? [{ runId: run.runId, name: run.name, status: run.status }] : [];
3990
- }
3991
- function failFinding(ruleId, message, evidence, expected, actual, meta) {
3992
- return {
3993
- ruleId,
3994
- severity: meta?.severity ?? "error",
3995
- status: meta?.status ?? "fail",
3996
- message,
3997
- ...expected !== void 0 ? { expected } : {},
3998
- ...actual !== void 0 ? { actual } : {},
3999
- evidence: [...evidence],
4000
- ...meta?.category !== void 0 ? { category: meta.category } : {},
4001
- ...meta?.confidence !== void 0 ? { confidence: meta.confidence } : {},
4002
- ...meta?.detector !== void 0 ? { detector: meta.detector } : {},
4003
- ...meta?.action !== void 0 ? { action: meta.action } : {}
4004
- };
4005
- }
4006
- function semanticEvents(context) {
4007
- return context.logicalEvents ?? context.events;
4008
- }
4009
- function isRecord8(value) {
4010
- return typeof value === "object" && value !== null && !Array.isArray(value);
4011
- }
4012
- function normalizedKey(value) {
4013
- return value.toLowerCase().replace(/[^a-z0-9_]/g, "");
4014
- }
4015
- function lastPathSegment(path16) {
4016
- const parts = path16.split(".");
4017
- return parts[parts.length - 1] ?? path16;
4018
- }
4019
- function valueType(value) {
4020
- if (Array.isArray(value)) return "array";
4021
- if (value === null) return "null";
4022
- return typeof value;
4023
- }
4024
- function serializedByteLength(value) {
4025
- try {
4026
- return Buffer.byteLength(JSON.stringify(value), "utf-8");
4027
- } catch {
4028
- return void 0;
4029
- }
4030
- }
4031
- function pushValueEntries(entries, event, value, path16, key, depth = 0) {
4032
- entries.push({ event, path: path16, key, value });
4033
- if (depth >= 8) return;
4034
- if (Array.isArray(value)) {
4035
- for (const [index, item] of value.entries()) {
4036
- pushValueEntries(entries, event, item, `${path16}.${index}`, String(index), depth + 1);
4037
- }
4038
- return;
4039
- }
4040
- if (!isRecord8(value)) return;
4041
- for (const nestedKey of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
4042
- pushValueEntries(
4043
- entries,
4044
- event,
4045
- value[nestedKey],
4046
- `${path16}.${nestedKey}`,
4047
- nestedKey,
4048
- depth + 1
4049
- );
4050
- }
4051
- }
4052
- function eventValueEntries(event, options = {}) {
4053
- const entries = [];
4054
- if (event.attributes !== void 0) {
4055
- pushValueEntries(entries, event, event.attributes, "attributes", "attributes");
4056
- }
4057
- if (options.includeSummaries) {
4058
- if (event.inputSummary !== void 0) {
4059
- pushValueEntries(entries, event, event.inputSummary, "inputSummary", "inputSummary");
4060
- }
4061
- if (event.outputSummary !== void 0) {
4062
- pushValueEntries(entries, event, event.outputSummary, "outputSummary", "outputSummary");
4063
- }
4064
- }
4065
- if (options.includeError && event.error !== void 0) {
4066
- pushValueEntries(entries, event, event.error, "error", "error");
4067
- }
4068
- return entries;
4069
- }
4070
- function limitFindings(findings, maxFindings) {
4071
- if (maxFindings === void 0 || findings.length <= maxFindings) return findings;
4072
- return findings.slice(0, Math.max(0, maxFindings));
4073
- }
4074
- function hasRedactionMarker(value, markers) {
4075
- return markers.some((marker) => value.includes(marker)) || /^\[HASH:[A-Za-z0-9_-]+\]$/.test(value);
4076
- }
4077
- function isSensitiveKey(key, sensitiveKeys) {
4078
- return isCredentialSensitiveKey(key, sensitiveKeys);
4079
- }
4080
- function isRawContentKey(key, forbiddenKeys) {
4081
- if (!key) return false;
4082
- const normalized = normalizedKey(key);
4083
- return forbiddenKeys.some((forbidden) => normalized === normalizedKey(forbidden));
4084
- }
4085
- function isSafeRawContentMetricPath(path16, key, safePathPrefixes) {
4086
- const leaf = normalizedKey(key ?? lastPathSegment(path16));
4087
- if (!SAFE_USAGE_LEAF_KEYS.has(leaf)) return false;
4088
- const parts = path16.split(".").filter(Boolean);
4089
- if (parts.length < 2) return false;
4090
- const parent = parts[parts.length - 2] ?? "";
4091
- const parentNorm = normalizedKey(parent);
4092
- return safePathPrefixes.some((prefix) => parentNorm === normalizedKey(prefix));
4093
- }
4094
- function isRawContentPath(path16, key, forbiddenKeys, safePathPrefixes) {
4095
- if (isSafeRawContentMetricPath(path16, key, safePathPrefixes)) return false;
4096
- return isRawContentKey(key ?? lastPathSegment(path16), forbiddenKeys);
4097
- }
4098
- function createRunStatusRule(options = {}) {
4099
- const expected = options.expected ?? "ok";
4100
- const allowIncomplete = options.allowIncomplete === true;
4101
- return {
4102
- id: "run.status",
4103
- category: "run",
4104
- defaultSeverity: "error",
4105
- evaluate(context) {
4106
- const findings = [];
4107
- const actual = context.selectedRun?.status ?? "unknown";
4108
- if (actual !== expected) {
4109
- findings.push(
4110
- failFinding(
4111
- "run.status",
4112
- `Run status ${actual} did not match expected ${expected}.`,
4113
- runEvidence(context.selectedRun),
4114
- expected,
4115
- actual
4116
- )
4117
- );
4118
- }
4119
- if (!allowIncomplete) {
4120
- const running = semanticEvents(context).filter((event) => event.status === "running");
4121
- if (running.length > 0) {
4122
- findings.push(
4123
- failFinding(
4124
- "run.status",
4125
- "Run contains incomplete running events.",
4126
- running.map((event) => eventEvidence(event)),
4127
- "no running events",
4128
- running.length
4129
- )
4130
- );
4131
- }
4132
- }
4133
- return findings;
3688
+ );
3689
+ }
3690
+ if (!allowIncomplete) {
3691
+ const running = semanticEvents(context).filter((event) => event.status === "running");
3692
+ if (running.length > 0) {
3693
+ findings.push(
3694
+ failFinding(
3695
+ "run.status",
3696
+ "Run contains incomplete running events.",
3697
+ running.map((event) => eventEvidence(event)),
3698
+ "no running events",
3699
+ running.length
3700
+ )
3701
+ );
3702
+ }
3703
+ }
3704
+ return findings;
4134
3705
  }
4135
3706
  };
4136
3707
  }
@@ -4240,168 +3811,638 @@ function createSafetySecretPatternRule(options = {}) {
4240
3811
  }
4241
3812
  return limitFindings(findings, options.maxFindings);
4242
3813
  }
4243
- };
3814
+ };
3815
+ }
3816
+ function createSafetyOversizedAttributeRule(options) {
3817
+ return {
3818
+ id: "safety.oversizedAttribute",
3819
+ category: "safety",
3820
+ defaultSeverity: "error",
3821
+ evaluate(context) {
3822
+ const findings = [];
3823
+ for (const event of context.events) {
3824
+ for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
3825
+ if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
3826
+ findings.push(
3827
+ failFinding(
3828
+ "safety.oversizedAttribute",
3829
+ `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
3830
+ [eventEvidence(event, entry.path)],
3831
+ { maxStringLength: options.maxStringLength },
3832
+ { path: entry.path, length: entry.value.length },
3833
+ {
3834
+ category: "size",
3835
+ confidence: "high",
3836
+ detector: "safety.oversizedAttribute",
3837
+ action: "truncate-or-omit"
3838
+ }
3839
+ )
3840
+ );
3841
+ }
3842
+ if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
3843
+ findings.push(
3844
+ failFinding(
3845
+ "safety.oversizedAttribute",
3846
+ `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
3847
+ [eventEvidence(event, entry.path)],
3848
+ { maxArrayLength: options.maxArrayLength },
3849
+ { path: entry.path, length: entry.value.length },
3850
+ {
3851
+ category: "size",
3852
+ confidence: "high",
3853
+ detector: "safety.oversizedAttribute",
3854
+ action: "truncate-or-omit"
3855
+ }
3856
+ )
3857
+ );
3858
+ }
3859
+ if (isRecord8(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
3860
+ findings.push(
3861
+ failFinding(
3862
+ "safety.oversizedAttribute",
3863
+ `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
3864
+ [eventEvidence(event, entry.path)],
3865
+ { maxObjectKeys: options.maxObjectKeys },
3866
+ { path: entry.path, keys: Object.keys(entry.value).length },
3867
+ {
3868
+ category: "size",
3869
+ confidence: "high",
3870
+ detector: "safety.oversizedAttribute",
3871
+ action: "truncate-or-omit"
3872
+ }
3873
+ )
3874
+ );
3875
+ }
3876
+ if (options.maxSerializedBytes !== void 0) {
3877
+ const bytes = serializedByteLength(entry.value);
3878
+ if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
3879
+ findings.push(
3880
+ failFinding(
3881
+ "safety.oversizedAttribute",
3882
+ `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
3883
+ [eventEvidence(event, entry.path)],
3884
+ { maxSerializedBytes: options.maxSerializedBytes },
3885
+ { path: entry.path, bytes },
3886
+ {
3887
+ category: "size",
3888
+ confidence: "high",
3889
+ detector: "safety.oversizedAttribute",
3890
+ action: "truncate-or-omit"
3891
+ }
3892
+ )
3893
+ );
3894
+ }
3895
+ }
3896
+ }
3897
+ }
3898
+ return limitFindings(findings, options.maxFindings);
3899
+ }
3900
+ };
3901
+ }
3902
+ function runTraceChecks(input, options = {}) {
3903
+ const selected = resolveSelectedRun(input, options.runId);
3904
+ if (selected.diagnostics.length > 0) {
3905
+ return errorResult(input, selected.diagnostics, selected.run);
3906
+ }
3907
+ const rules = selectRules(options.rules ?? [], options.select);
3908
+ if (rules.diagnostics.length > 0) {
3909
+ return errorResult(input, rules.diagnostics, selected.run);
3910
+ }
3911
+ if (rules.rules.length === 0) {
3912
+ return errorResult(
3913
+ input,
3914
+ [
3915
+ diagnostic(
3916
+ "AI_CHECK_NO_RULES_EVALUATED",
3917
+ "No trace check rules were evaluated. Configure at least one rule, contract, or CLI check option."
3918
+ )
3919
+ ],
3920
+ selected.run
3921
+ );
3922
+ }
3923
+ const facts = buildFacts(input, selected.run);
3924
+ const context = {
3925
+ ...facts,
3926
+ ...selected.run ? { selectedRun: selected.run } : {},
3927
+ ...input.sourceLabel ? { sourceLabel: input.sourceLabel } : {}
3928
+ };
3929
+ const diagnostics = [];
3930
+ const findings = [];
3931
+ const ruleExecutions = [];
3932
+ for (const rule of rules.rules) {
3933
+ try {
3934
+ const ruleFindings = rule.evaluate(context).map((finding) => normalizeFinding(rule, finding));
3935
+ findings.push(...ruleFindings);
3936
+ ruleExecutions.push({
3937
+ ruleId: rule.id,
3938
+ category: rule.category,
3939
+ status: classifyRuleExecution(ruleFindings, false),
3940
+ findingCount: ruleFindings.length,
3941
+ ...selected.run ? { runId: selected.run.runId } : {}
3942
+ });
3943
+ } catch (error) {
3944
+ const message = error instanceof Error ? error.message : String(error);
3945
+ diagnostics.push(
3946
+ diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
3947
+ );
3948
+ ruleExecutions.push({
3949
+ ruleId: rule.id,
3950
+ category: rule.category,
3951
+ status: "error",
3952
+ findingCount: 0,
3953
+ ...selected.run ? { runId: selected.run.runId } : {}
3954
+ });
3955
+ }
3956
+ }
3957
+ if (diagnostics.length > 0) {
3958
+ return errorResult(input, diagnostics, selected.run, ruleExecutions);
3959
+ }
3960
+ const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
3961
+ const sortedFindings = findings.sort(compareFindings(eventById));
3962
+ const summary = summarize(sortedFindings, diagnostics, ruleExecutions.length);
3963
+ const status = summary.failed > 0 ? "fail" : "pass";
3964
+ return {
3965
+ ok: status === "pass",
3966
+ status,
3967
+ format: input.read.format,
3968
+ ...selected.run ? { runId: selected.run.runId } : {},
3969
+ summary,
3970
+ findings: sortedFindings,
3971
+ diagnostics,
3972
+ ruleExecutions
3973
+ };
3974
+ }
3975
+
3976
+ // packages/core/src/checks/contract.ts
3977
+ new Set(OBSERVED_OUTCOME_METHODS);
3978
+
3979
+ // packages/core/src/exporters/helpers.ts
3980
+ var REDACT_SUBSTRINGS = [
3981
+ "authorization",
3982
+ "cookie",
3983
+ "token",
3984
+ "apikey",
3985
+ "password",
3986
+ "secret",
3987
+ "email"
3988
+ ];
3989
+ function shouldRedactKey(key) {
3990
+ const k = key.toLowerCase();
3991
+ for (const s of REDACT_SUBSTRINGS) {
3992
+ if (k.includes(s)) return true;
3993
+ }
3994
+ return false;
3995
+ }
3996
+ function safeString(value, maxLength) {
3997
+ if (value === null || value === void 0) return "";
3998
+ let s;
3999
+ if (typeof value === "string") s = value;
4000
+ else if (typeof value === "number" || typeof value === "boolean") s = String(value);
4001
+ else s = stableJson(value, false);
4002
+ if (maxLength !== void 0 && maxLength >= 0 && s.length > maxLength) {
4003
+ return `${s.slice(0, maxLength)}\u2026`;
4004
+ }
4005
+ return s;
4006
+ }
4007
+ function escapeMarkdown(value) {
4008
+ return value.replace(/\|/g, "\\|").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, " ");
4009
+ }
4010
+ function escapeHtml(value) {
4011
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4012
+ }
4013
+ function sortKeysDeep(input) {
4014
+ if (input === null || typeof input !== "object") return input;
4015
+ if (Array.isArray(input)) return input.map(sortKeysDeep);
4016
+ const o = input;
4017
+ const out = {};
4018
+ for (const k of Object.keys(o).sort()) {
4019
+ out[k] = sortKeysDeep(o[k]);
4020
+ }
4021
+ return out;
4022
+ }
4023
+ function stableJson(value, pretty) {
4024
+ const sorted = sortKeysDeep(value);
4025
+ return pretty === true ? JSON.stringify(sorted, null, 2) : JSON.stringify(sorted);
4026
+ }
4027
+ function compactAttributes(attrs2, options) {
4028
+ if (attrs2 === void 0) return {};
4029
+ const maxLen = options?.maxLength ?? 500;
4030
+ const redacted = options?.redacted ?? true;
4031
+ const out = {};
4032
+ for (const key of Object.keys(attrs2).sort()) {
4033
+ if (redacted && shouldRedactKey(key)) {
4034
+ out[key] = "[REDACTED]";
4035
+ continue;
4036
+ }
4037
+ const v = attrs2[key];
4038
+ out[key] = compactValue(v, maxLen, redacted);
4039
+ }
4040
+ return out;
4041
+ }
4042
+ function compactValue(value, maxLen, redacted) {
4043
+ if (value === null || typeof value !== "object") {
4044
+ return typeof value === "string" ? safeString(value, maxLen) : value;
4045
+ }
4046
+ if (Array.isArray(value)) {
4047
+ const arr = value.slice(0, 20).map((x) => compactValue(x, maxLen, redacted));
4048
+ if (value.length > 20) arr.push(`\u2026(+${value.length - 20} more)`);
4049
+ return arr;
4050
+ }
4051
+ const o = value;
4052
+ const inner = {};
4053
+ for (const k of Object.keys(o)) {
4054
+ if (redacted && shouldRedactKey(k)) inner[k] = "[REDACTED]";
4055
+ else inner[k] = compactValue(o[k], maxLen, redacted);
4056
+ }
4057
+ return inner;
4058
+ }
4059
+ function flattenTree(tree) {
4060
+ const out = [];
4061
+ function walk(nodes) {
4062
+ for (const n of nodes) {
4063
+ out.push(n);
4064
+ if (n.children.length > 0) walk(n.children);
4065
+ }
4066
+ }
4067
+ walk(tree.children);
4068
+ return out;
4069
+ }
4070
+
4071
+ // packages/core/src/diff/comparable.ts
4072
+ function extractOutputPreview(meta) {
4073
+ if (meta === void 0) return void 0;
4074
+ if ("outputPreview" in meta) return meta.outputPreview;
4075
+ if ("resultPreview" in meta) return meta.resultPreview;
4076
+ return void 0;
4077
+ }
4078
+ function mapStepStatus(s) {
4079
+ if (s === void 0) return "running";
4080
+ return s;
4081
+ }
4082
+ function manualTraceEventsToComparableRun(events) {
4083
+ const started = events.find((e) => e.event === "run_started");
4084
+ if (!started || started.event !== "run_started") {
4085
+ throw new Error("Invalid trace: missing run_started");
4086
+ }
4087
+ const rs = started;
4088
+ const runId = rs.runId;
4089
+ const completedAll = events.filter((e) => e.event === "run_completed");
4090
+ const lastCompleted = completedAll[completedAll.length - 1];
4091
+ let runStatus;
4092
+ if (lastCompleted === void 0) runStatus = "running";
4093
+ else runStatus = lastCompleted.status;
4094
+ const durationMs = lastCompleted !== void 0 && Number.isFinite(lastCompleted.durationMs) ? lastCompleted.durationMs : void 0;
4095
+ const steps = /* @__PURE__ */ new Map();
4096
+ let order = 0;
4097
+ for (const e of events) {
4098
+ if (e.event !== "step_started") continue;
4099
+ const s = e;
4100
+ const meta = s.metadata ? { ...s.metadata } : void 0;
4101
+ steps.set(s.stepId, {
4102
+ id: s.stepId,
4103
+ parentId: s.parentId,
4104
+ name: s.name,
4105
+ type: s.type,
4106
+ order: order++,
4107
+ timestamp: s.timestamp,
4108
+ metadata: meta
4109
+ });
4110
+ }
4111
+ for (const e of events) {
4112
+ if (e.event !== "step_completed") continue;
4113
+ const acc = steps.get(e.stepId);
4114
+ if (!acc) continue;
4115
+ acc.status = e.status;
4116
+ acc.durationMs = e.durationMs;
4117
+ if (e.error?.message) acc.errorMsg = e.error.message;
4118
+ const extra = e;
4119
+ if (extra.metadata !== void 0 && typeof extra.metadata === "object") {
4120
+ acc.metadata = { ...acc.metadata ?? {}, ...extra.metadata };
4121
+ }
4122
+ }
4123
+ const nodes = /* @__PURE__ */ new Map();
4124
+ for (const acc of steps.values()) {
4125
+ let meta = acc.metadata ? { ...acc.metadata } : void 0;
4126
+ if (acc.parentId !== void 0 && !steps.has(acc.parentId)) {
4127
+ meta = { ...meta ?? {}, agent_inspect_diff_parent_missing: true };
4128
+ }
4129
+ const outputPreview = extractOutputPreview(meta);
4130
+ if (meta !== void 0 && ("outputPreview" in meta || "resultPreview" in meta)) {
4131
+ delete meta.outputPreview;
4132
+ delete meta.resultPreview;
4133
+ }
4134
+ const sc = {
4135
+ id: acc.id,
4136
+ name: acc.name,
4137
+ type: acc.type,
4138
+ status: mapStepStatus(acc.status),
4139
+ durationMs: acc.durationMs,
4140
+ error: acc.errorMsg,
4141
+ metadata: meta && Object.keys(meta).length > 0 ? meta : void 0,
4142
+ outputPreview,
4143
+ children: []
4144
+ };
4145
+ nodes.set(acc.id, sc);
4146
+ }
4147
+ const roots = [];
4148
+ const sortByOrder = (a, b) => {
4149
+ const oa = steps.get(a.id)?.order ?? 0;
4150
+ const ob = steps.get(b.id)?.order ?? 0;
4151
+ return oa - ob;
4152
+ };
4153
+ for (const acc of steps.values()) {
4154
+ const node = nodes.get(acc.id);
4155
+ if (acc.parentId !== void 0 && nodes.has(acc.parentId)) {
4156
+ nodes.get(acc.parentId).children.push(node);
4157
+ } else {
4158
+ roots.push(node);
4159
+ }
4160
+ }
4161
+ roots.sort(sortByOrder);
4162
+ for (const n of nodes.values()) {
4163
+ n.children.sort(sortByOrder);
4164
+ }
4165
+ return {
4166
+ runId,
4167
+ name: rs.name,
4168
+ status: runStatus,
4169
+ durationMs,
4170
+ steps: roots
4171
+ };
4172
+ }
4173
+
4174
+ // packages/core/src/diff/engine.ts
4175
+ var DEFAULT_THRESHOLD_MS = 0;
4176
+ function pathSeg(step, index) {
4177
+ return { index, name: step.name, stepId: step.id };
4178
+ }
4179
+ function buildPath(segments) {
4180
+ return { path: [...segments] };
4181
+ }
4182
+ function pairSteps(left, right) {
4183
+ const usedRight = /* @__PURE__ */ new Set();
4184
+ const pairs = [];
4185
+ for (let i = 0; i < left.length; i++) {
4186
+ const L = left[i];
4187
+ let R = right.find((r) => !usedRight.has(r.id) && r.id === L.id);
4188
+ if (R === void 0 && i < right.length && !usedRight.has(right[i].id)) {
4189
+ const cand = right[i];
4190
+ if (cand.name === L.name && (cand.type ?? "") === (L.type ?? "")) {
4191
+ R = cand;
4192
+ }
4193
+ }
4194
+ if (R === void 0) {
4195
+ R = right.find(
4196
+ (r) => !usedRight.has(r.id) && r.name === L.name && (r.type ?? "") === (L.type ?? "")
4197
+ );
4198
+ }
4199
+ if (R !== void 0) {
4200
+ usedRight.add(R.id);
4201
+ pairs.push([L, R]);
4202
+ } else {
4203
+ pairs.push([L, void 0]);
4204
+ }
4205
+ }
4206
+ for (const R of right) {
4207
+ if (!usedRight.has(R.id)) {
4208
+ pairs.push([void 0, R]);
4209
+ }
4210
+ }
4211
+ return pairs;
4212
+ }
4213
+ function compareLeafSteps(L, R, segments, opts, out) {
4214
+ const path16 = buildPath(segments);
4215
+ if (L.name !== R.name) {
4216
+ out.push({
4217
+ kind: "structure",
4218
+ severity: "warning",
4219
+ message: "Step name differs",
4220
+ path: path16,
4221
+ left: L.name,
4222
+ right: R.name
4223
+ });
4224
+ }
4225
+ if ((L.type ?? "") !== (R.type ?? "")) {
4226
+ out.push({
4227
+ kind: "step-type",
4228
+ severity: "warning",
4229
+ message: "Step type differs",
4230
+ path: path16,
4231
+ left: L.type,
4232
+ right: R.type
4233
+ });
4234
+ }
4235
+ if ((L.status ?? "") !== (R.status ?? "")) {
4236
+ out.push({
4237
+ kind: "step-status",
4238
+ severity: "warning",
4239
+ message: "Step status differs",
4240
+ path: path16,
4241
+ left: L.status,
4242
+ right: R.status
4243
+ });
4244
+ }
4245
+ const le = L.error ?? "";
4246
+ const re = R.error ?? "";
4247
+ if (le !== re) {
4248
+ out.push({
4249
+ kind: "error",
4250
+ severity: "error",
4251
+ message: "Step error message differs",
4252
+ path: path16,
4253
+ left: le || void 0,
4254
+ right: re || void 0
4255
+ });
4256
+ }
4257
+ if (!opts.ignoreDuration) {
4258
+ const ld = L.durationMs;
4259
+ const rd = R.durationMs;
4260
+ const th = opts.durationThresholdMs;
4261
+ let differs = false;
4262
+ if (ld === void 0 && rd === void 0) differs = false;
4263
+ else if (ld === void 0 || rd === void 0) differs = true;
4264
+ else differs = Math.abs(ld - rd) > th;
4265
+ if (differs) {
4266
+ out.push({
4267
+ kind: "duration",
4268
+ severity: "info",
4269
+ message: "Step duration differs",
4270
+ path: path16,
4271
+ left: ld,
4272
+ right: rd
4273
+ });
4274
+ }
4275
+ }
4276
+ const lm = stableJson(L.metadata ?? {});
4277
+ const rm = stableJson(R.metadata ?? {});
4278
+ if (lm !== rm) {
4279
+ out.push({
4280
+ kind: "metadata",
4281
+ severity: "info",
4282
+ message: "Step metadata differs",
4283
+ path: path16,
4284
+ left: L.metadata,
4285
+ right: R.metadata
4286
+ });
4287
+ }
4288
+ const lo = stableJson(L.outputPreview ?? null);
4289
+ const ro = stableJson(R.outputPreview ?? null);
4290
+ if (lo !== ro) {
4291
+ out.push({
4292
+ kind: "output",
4293
+ severity: "info",
4294
+ message: "Output preview differs",
4295
+ path: path16,
4296
+ left: L.outputPreview,
4297
+ right: R.outputPreview
4298
+ });
4299
+ }
4300
+ }
4301
+ function compareRecursive(L, R, segments, opts, out) {
4302
+ compareLeafSteps(L, R, segments, opts, out);
4303
+ const pairs = pairSteps(L.children, R.children);
4304
+ let ci = 0;
4305
+ for (const [lch, rch] of pairs) {
4306
+ if (lch !== void 0 && rch !== void 0) {
4307
+ compareRecursive(lch, rch, [...segments, pathSeg(lch, ci)], opts, out);
4308
+ } else if (lch !== void 0) {
4309
+ out.push({
4310
+ kind: "step-removed",
4311
+ severity: "warning",
4312
+ message: `Step only in left run: ${lch.name}`,
4313
+ path: buildPath([...segments, pathSeg(lch, ci)]),
4314
+ left: lch.id,
4315
+ right: void 0
4316
+ });
4317
+ } else if (rch !== void 0) {
4318
+ out.push({
4319
+ kind: "step-added",
4320
+ severity: "warning",
4321
+ message: `Step only in right run: ${rch.name}`,
4322
+ path: buildPath([...segments, pathSeg(rch, ci)]),
4323
+ left: void 0,
4324
+ right: rch.id
4325
+ });
4326
+ }
4327
+ ci += 1;
4328
+ }
4244
4329
  }
4245
- function createSafetyOversizedAttributeRule(options) {
4330
+ function mergeDiffDefaults(options) {
4246
4331
  return {
4247
- id: "safety.oversizedAttribute",
4248
- category: "safety",
4249
- defaultSeverity: "error",
4250
- evaluate(context) {
4251
- const findings = [];
4252
- for (const event of context.events) {
4253
- for (const entry of eventValueEntries(event, { includeSummaries: true, includeError: true })) {
4254
- if (typeof entry.value === "string" && options.maxStringLength !== void 0 && entry.value.length > options.maxStringLength) {
4255
- findings.push(
4256
- failFinding(
4257
- "safety.oversizedAttribute",
4258
- `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
4259
- [eventEvidence(event, entry.path)],
4260
- { maxStringLength: options.maxStringLength },
4261
- { path: entry.path, length: entry.value.length },
4262
- {
4263
- category: "size",
4264
- confidence: "high",
4265
- detector: "safety.oversizedAttribute",
4266
- action: "truncate-or-omit"
4267
- }
4268
- )
4269
- );
4270
- }
4271
- if (Array.isArray(entry.value) && options.maxArrayLength !== void 0 && entry.value.length > options.maxArrayLength) {
4272
- findings.push(
4273
- failFinding(
4274
- "safety.oversizedAttribute",
4275
- `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
4276
- [eventEvidence(event, entry.path)],
4277
- { maxArrayLength: options.maxArrayLength },
4278
- { path: entry.path, length: entry.value.length },
4279
- {
4280
- category: "size",
4281
- confidence: "high",
4282
- detector: "safety.oversizedAttribute",
4283
- action: "truncate-or-omit"
4284
- }
4285
- )
4286
- );
4287
- }
4288
- if (isRecord8(entry.value) && options.maxObjectKeys !== void 0 && Object.keys(entry.value).length > options.maxObjectKeys) {
4289
- findings.push(
4290
- failFinding(
4291
- "safety.oversizedAttribute",
4292
- `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
4293
- [eventEvidence(event, entry.path)],
4294
- { maxObjectKeys: options.maxObjectKeys },
4295
- { path: entry.path, keys: Object.keys(entry.value).length },
4296
- {
4297
- category: "size",
4298
- confidence: "high",
4299
- detector: "safety.oversizedAttribute",
4300
- action: "truncate-or-omit"
4301
- }
4302
- )
4303
- );
4304
- }
4305
- if (options.maxSerializedBytes !== void 0) {
4306
- const bytes = serializedByteLength(entry.value);
4307
- if (bytes !== void 0 && bytes > options.maxSerializedBytes) {
4308
- findings.push(
4309
- failFinding(
4310
- "safety.oversizedAttribute",
4311
- `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
4312
- [eventEvidence(event, entry.path)],
4313
- { maxSerializedBytes: options.maxSerializedBytes },
4314
- { path: entry.path, bytes },
4315
- {
4316
- category: "size",
4317
- confidence: "high",
4318
- detector: "safety.oversizedAttribute",
4319
- action: "truncate-or-omit"
4320
- }
4321
- )
4322
- );
4323
- }
4324
- }
4325
- }
4326
- }
4327
- return limitFindings(findings, options.maxFindings);
4328
- }
4332
+ ignoreDuration: false,
4333
+ durationThresholdMs: DEFAULT_THRESHOLD_MS,
4334
+ focus: "all",
4335
+ check: "all"
4329
4336
  };
4330
4337
  }
4331
- function runTraceChecks(input, options = {}) {
4332
- const selected = resolveSelectedRun(input, options.runId);
4333
- if (selected.diagnostics.length > 0) {
4334
- return errorResult(input, selected.diagnostics, selected.run);
4335
- }
4336
- const rules = selectRules(options.rules ?? [], options.select);
4337
- if (rules.diagnostics.length > 0) {
4338
- return errorResult(input, rules.diagnostics, selected.run);
4338
+ function kindMatchesFilter(kind, merged) {
4339
+ return true;
4340
+ }
4341
+ function diffRuns(left, right, options) {
4342
+ const merged = mergeDiffDefaults();
4343
+ const opts = {
4344
+ ignoreDuration: merged.ignoreDuration,
4345
+ durationThresholdMs: merged.durationThresholdMs
4346
+ };
4347
+ const raw = [];
4348
+ if ((left.status ?? "") !== (right.status ?? "")) {
4349
+ raw.push({
4350
+ kind: "run-status",
4351
+ severity: "warning",
4352
+ message: "Run completion status differs",
4353
+ left: left.status,
4354
+ right: right.status
4355
+ });
4339
4356
  }
4340
- if (rules.rules.length === 0) {
4341
- return errorResult(
4342
- input,
4343
- [
4344
- diagnostic(
4345
- "AI_CHECK_NO_RULES_EVALUATED",
4346
- "No trace check rules were evaluated. Configure at least one rule, contract, or CLI check option."
4347
- )
4348
- ],
4349
- selected.run
4350
- );
4357
+ {
4358
+ const ld = left.durationMs;
4359
+ const rd = right.durationMs;
4360
+ const th = merged.durationThresholdMs;
4361
+ let differs = false;
4362
+ if (ld === void 0 && rd === void 0) differs = false;
4363
+ else if (ld === void 0 || rd === void 0) differs = true;
4364
+ else differs = Math.abs(ld - rd) > th;
4365
+ if (differs) {
4366
+ raw.push({
4367
+ kind: "duration",
4368
+ severity: "info",
4369
+ message: "Run duration differs",
4370
+ left: ld,
4371
+ right: rd
4372
+ });
4373
+ }
4351
4374
  }
4352
- const facts = buildFacts(input, selected.run);
4353
- const context = {
4354
- ...facts,
4355
- ...selected.run ? { selectedRun: selected.run } : {},
4356
- ...input.sourceLabel ? { sourceLabel: input.sourceLabel } : {}
4357
- };
4358
- const diagnostics = [];
4359
- const findings = [];
4360
- const ruleExecutions = [];
4361
- for (const rule of rules.rules) {
4362
- try {
4363
- const ruleFindings = rule.evaluate(context).map((finding) => normalizeFinding(rule, finding));
4364
- findings.push(...ruleFindings);
4365
- ruleExecutions.push({
4366
- ruleId: rule.id,
4367
- category: rule.category,
4368
- status: classifyRuleExecution(ruleFindings, false),
4369
- findingCount: ruleFindings.length,
4370
- ...selected.run ? { runId: selected.run.runId } : {}
4375
+ const pairs = pairSteps(left.steps, right.steps);
4376
+ let idx = 0;
4377
+ for (const [ls, rs] of pairs) {
4378
+ if (ls !== void 0 && rs !== void 0) {
4379
+ compareRecursive(ls, rs, [pathSeg(ls, idx)], opts, raw);
4380
+ idx += 1;
4381
+ } else if (ls !== void 0) {
4382
+ raw.push({
4383
+ kind: "step-removed",
4384
+ severity: "warning",
4385
+ message: `Step only in left run: ${ls.name}`,
4386
+ path: buildPath([pathSeg(ls, idx)]),
4387
+ left: ls.id,
4388
+ right: void 0
4371
4389
  });
4372
- } catch (error) {
4373
- const message = error instanceof Error ? error.message : String(error);
4374
- diagnostics.push(
4375
- diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
4376
- );
4377
- ruleExecutions.push({
4378
- ruleId: rule.id,
4379
- category: rule.category,
4380
- status: "error",
4381
- findingCount: 0,
4382
- ...selected.run ? { runId: selected.run.runId } : {}
4390
+ idx += 1;
4391
+ } else if (rs !== void 0) {
4392
+ raw.push({
4393
+ kind: "step-added",
4394
+ severity: "warning",
4395
+ message: `Step only in right run: ${rs.name}`,
4396
+ path: buildPath([pathSeg(rs, idx)]),
4397
+ left: void 0,
4398
+ right: rs.id
4383
4399
  });
4400
+ idx += 1;
4384
4401
  }
4385
4402
  }
4386
- if (diagnostics.length > 0) {
4387
- return errorResult(input, diagnostics, selected.run, ruleExecutions);
4403
+ const differences = raw.filter((d) => kindMatchesFilter(d.kind));
4404
+ let errors = 0;
4405
+ let warnings = 0;
4406
+ let info = 0;
4407
+ for (const d of differences) {
4408
+ if (d.severity === "error") errors += 1;
4409
+ else if (d.severity === "warning") warnings += 1;
4410
+ else info += 1;
4388
4411
  }
4389
- const eventById = new Map(input.read.events.map((event) => [event.eventId, event]));
4390
- const sortedFindings = findings.sort(compareFindings(eventById));
4391
- const summary = summarize(sortedFindings, diagnostics, ruleExecutions.length);
4392
- const status = summary.failed > 0 ? "fail" : "pass";
4393
- return {
4394
- ok: status === "pass",
4395
- status,
4396
- format: input.read.format,
4397
- ...selected.run ? { runId: selected.run.runId } : {},
4398
- summary,
4399
- findings: sortedFindings,
4400
- diagnostics,
4401
- ruleExecutions
4412
+ const firstVisible = differences[0];
4413
+ const firstDivergence = firstVisible !== void 0 ? {
4414
+ kind: "first-divergence",
4415
+ severity: firstVisible.severity,
4416
+ message: `First divergence: ${firstVisible.message}`,
4417
+ path: firstVisible.path,
4418
+ left: firstVisible.left,
4419
+ right: firstVisible.right
4420
+ } : void 0;
4421
+ const summary = {
4422
+ leftRunId: left.runId,
4423
+ rightRunId: right.runId,
4424
+ totalDifferences: differences.length,
4425
+ errors,
4426
+ warnings,
4427
+ info,
4428
+ firstDivergence
4402
4429
  };
4430
+ return { summary, differences };
4403
4431
  }
4404
4432
 
4433
+ // packages/core/src/evidence/zip.ts
4434
+ (() => {
4435
+ const table = new Uint32Array(256);
4436
+ for (let n = 0; n < 256; n += 1) {
4437
+ let c = n;
4438
+ for (let k = 0; k < 8; k += 1) {
4439
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
4440
+ }
4441
+ table[n] = c >>> 0;
4442
+ }
4443
+ return table;
4444
+ })();
4445
+
4405
4446
  // packages/core/src/persisted/token-usage.ts
4406
4447
  function isRecord9(value) {
4407
4448
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -4415,16 +4456,20 @@ function normalizeTokenUsage(value) {
4415
4456
  const output = nonNegativeFinite(value.output);
4416
4457
  const suppliedTotal = nonNegativeFinite(value.total);
4417
4458
  const cached = nonNegativeFinite(value.cached);
4459
+ const cacheWrite = nonNegativeFinite(value.cacheWrite);
4460
+ const reasoning = nonNegativeFinite(value.reasoning);
4418
4461
  const derivedTotal = input !== void 0 && output !== void 0 && Number.isFinite(input + output) ? input + output : void 0;
4419
4462
  const total = suppliedTotal ?? derivedTotal;
4420
- if (input === void 0 && output === void 0 && total === void 0 && cached === void 0) {
4463
+ if (input === void 0 && output === void 0 && total === void 0 && cached === void 0 && cacheWrite === void 0 && reasoning === void 0) {
4421
4464
  return void 0;
4422
4465
  }
4423
4466
  return {
4424
4467
  ...input !== void 0 ? { input } : {},
4425
4468
  ...output !== void 0 ? { output } : {},
4426
4469
  ...total !== void 0 ? { total } : {},
4427
- ...cached !== void 0 ? { cached } : {}
4470
+ ...cached !== void 0 ? { cached } : {},
4471
+ ...cacheWrite !== void 0 ? { cacheWrite } : {},
4472
+ ...reasoning !== void 0 ? { reasoning } : {}
4428
4473
  };
4429
4474
  }
4430
4475
 
@@ -5583,6 +5628,8 @@ function readOpenInferenceTokenUsage(attributes) {
5583
5628
  const completion = attributes["llm.token_count.completion"];
5584
5629
  const total = attributes["llm.token_count.total"];
5585
5630
  const cached = attributes["llm.token_count.prompt_details.cache_read"];
5631
+ const cacheWrite = attributes["llm.token_count.prompt_details.cache_write"] ?? attributes["llm.token_count.prompt_details.cache_creation"];
5632
+ const reasoning = attributes["llm.token_count.completion_details.reasoning"] ?? attributes["llm.token_count.completion_details.reasoning_tokens"];
5586
5633
  const usage = {};
5587
5634
  if (typeof prompt === "number" && Number.isFinite(prompt) && prompt >= 0) {
5588
5635
  usage.input = prompt;
@@ -5596,6 +5643,12 @@ function readOpenInferenceTokenUsage(attributes) {
5596
5643
  if (typeof cached === "number" && Number.isFinite(cached) && cached >= 0) {
5597
5644
  usage.cached = cached;
5598
5645
  }
5646
+ if (typeof cacheWrite === "number" && Number.isFinite(cacheWrite) && cacheWrite >= 0) {
5647
+ usage.cacheWrite = cacheWrite;
5648
+ }
5649
+ if (typeof reasoning === "number" && Number.isFinite(reasoning) && reasoning >= 0) {
5650
+ usage.reasoning = reasoning;
5651
+ }
5599
5652
  if (usage.total === void 0 && usage.input !== void 0 && usage.output !== void 0) {
5600
5653
  usage.total = usage.input + usage.output;
5601
5654
  }