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