agent-inspect 6.26.0 → 6.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/docs/BUNDLES.md +4 -1
  4. package/docs/LIMITATIONS.md +1 -1
  5. package/docs/SAFE-TRACE-SHARING.md +2 -0
  6. package/docs/TRACE-CONTRACTS.md +21 -3
  7. package/package.json +1 -1
  8. package/packages/cli/dist/{chunk-HPR2OJPU.mjs → chunk-JVQ4LTHR.mjs} +4317 -3803
  9. package/packages/cli/dist/chunk-JVQ4LTHR.mjs.map +1 -0
  10. package/packages/cli/dist/index.cjs +4541 -4005
  11. package/packages/cli/dist/index.cjs.map +1 -1
  12. package/packages/cli/dist/index.mjs +4 -4
  13. package/packages/cli/dist/index.mjs.map +1 -1
  14. package/packages/cli/dist/{src-J43ECFHC.mjs → src-M5J5YC5E.mjs} +3 -3
  15. package/packages/cli/dist/{src-J43ECFHC.mjs.map → src-M5J5YC5E.mjs.map} +1 -1
  16. package/packages/core/dist/advanced.cjs +3026 -2501
  17. package/packages/core/dist/advanced.cjs.map +1 -1
  18. package/packages/core/dist/advanced.d.cts +172 -16
  19. package/packages/core/dist/advanced.d.ts +172 -16
  20. package/packages/core/dist/advanced.mjs +362 -32
  21. package/packages/core/dist/advanced.mjs.map +1 -1
  22. package/packages/core/dist/checks.cjs +537 -152
  23. package/packages/core/dist/checks.cjs.map +1 -1
  24. package/packages/core/dist/checks.d.cts +9 -414
  25. package/packages/core/dist/checks.d.ts +9 -414
  26. package/packages/core/dist/checks.mjs +1 -1
  27. package/packages/core/dist/{chunk-LHSXRIYU.mjs → chunk-ME4WXYY3.mjs} +3609 -3224
  28. package/packages/core/dist/chunk-ME4WXYY3.mjs.map +1 -0
  29. package/packages/core/dist/{context-DOOT9GZ-.d.cts → context-DGMqxFpz.d.cts} +4 -4
  30. package/packages/core/dist/{context-DOX2-RjH.d.ts → context-DgcvkJ9t.d.ts} +4 -4
  31. package/packages/core/dist/diff.d.cts +3 -3
  32. package/packages/core/dist/diff.d.ts +3 -3
  33. package/packages/core/dist/exporters.d.cts +3 -3
  34. package/packages/core/dist/exporters.d.ts +3 -3
  35. package/packages/core/dist/{index-CtK_p-ps.d.ts → index-BOA1AeEU.d.ts} +473 -6
  36. package/packages/core/dist/{index-D72DPqAF.d.ts → index-BzyNPfLm.d.ts} +2 -2
  37. package/packages/core/dist/{index-DLLz9sce.d.cts → index-C6bCIbFO.d.cts} +2 -2
  38. package/packages/core/dist/{index-CR4CCRqP.d.cts → index-DVetZwGG.d.cts} +473 -6
  39. package/packages/core/dist/index.d.cts +7 -7
  40. package/packages/core/dist/index.d.ts +7 -7
  41. package/packages/core/dist/{inspect-event-DRthdZlf.d.cts → inspect-event-CDqjcrG_.d.cts} +1 -1
  42. package/packages/core/dist/{inspect-event-DRthdZlf.d.ts → inspect-event-CDqjcrG_.d.ts} +1 -1
  43. package/packages/core/dist/{log-config-CFlIJHTf.d.cts → log-config-DcjV8ZjF.d.cts} +1 -1
  44. package/packages/core/dist/{log-config-OZRfpHED.d.ts → log-config-N_c5AE09.d.ts} +1 -1
  45. package/packages/core/dist/logs.d.cts +3 -3
  46. package/packages/core/dist/logs.d.ts +3 -3
  47. package/packages/core/dist/{persisted-inspect-event-BVucFVrP.d.ts → persisted-inspect-event-CbaRYpbw.d.ts} +1 -1
  48. package/packages/core/dist/{persisted-inspect-event-CVZWZldL.d.cts → persisted-inspect-event-DiJ0I_vi.d.cts} +1 -1
  49. package/packages/core/dist/persisted.d.cts +5 -5
  50. package/packages/core/dist/persisted.d.ts +5 -5
  51. package/packages/core/dist/readers.d.cts +3 -3
  52. package/packages/core/dist/readers.d.ts +3 -3
  53. package/packages/core/dist/{types-B562HgN_.d.cts → types-BA_IOdg4.d.cts} +1 -1
  54. package/packages/core/dist/{types-B562HgN_.d.ts → types-BA_IOdg4.d.ts} +1 -1
  55. package/packages/core/dist/{types-VzKihKQo.d.cts → types-wXuxMUop.d.cts} +2 -2
  56. package/packages/core/dist/{types-Bx3mEZ04.d.ts → types-xv9bSgOK.d.ts} +2 -2
  57. package/packages/core/dist/writers.d.cts +2 -2
  58. package/packages/core/dist/writers.d.ts +2 -2
  59. package/packages/cli/dist/chunk-HPR2OJPU.mjs.map +0 -1
  60. package/packages/core/dist/chunk-LHSXRIYU.mjs.map +0 -1
@@ -1462,6 +1462,133 @@ function evaluateControlRules(events, rules, runEvidence2, observationNames) {
1462
1462
  return findings;
1463
1463
  }
1464
1464
 
1465
+ // packages/core/src/checks/tool-arguments.ts
1466
+ function decodeToken(token) {
1467
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
1468
+ }
1469
+ function resolveJsonPointer(document, pointer) {
1470
+ if (pointer === "") {
1471
+ return { found: true, value: document };
1472
+ }
1473
+ if (!pointer.startsWith("/")) {
1474
+ return { found: false };
1475
+ }
1476
+ const tokens = pointer.slice(1).split("/").map(decodeToken);
1477
+ let current = document;
1478
+ for (const token of tokens) {
1479
+ if (current === null || current === void 0) {
1480
+ return { found: false };
1481
+ }
1482
+ if (Array.isArray(current)) {
1483
+ if (!/^(0|[1-9][0-9]*)$/.test(token)) {
1484
+ return { found: false };
1485
+ }
1486
+ const index = Number(token);
1487
+ if (index >= current.length) {
1488
+ return { found: false };
1489
+ }
1490
+ current = current[index];
1491
+ continue;
1492
+ }
1493
+ if (typeof current !== "object") {
1494
+ return { found: false };
1495
+ }
1496
+ const record = current;
1497
+ if (!Object.prototype.hasOwnProperty.call(record, token)) {
1498
+ return { found: false };
1499
+ }
1500
+ current = record[token];
1501
+ }
1502
+ return { found: true, value: current };
1503
+ }
1504
+ function jsonType(value) {
1505
+ if (value === null) return "null";
1506
+ if (Array.isArray(value)) return "array";
1507
+ return typeof value;
1508
+ }
1509
+ function valuesEqual(left, right) {
1510
+ return JSON.stringify(left) === JSON.stringify(right);
1511
+ }
1512
+ function extractToolArgumentPayload(event) {
1513
+ const attrs2 = event.attributes ?? {};
1514
+ for (const key of ["arguments", "input", "toolArguments"]) {
1515
+ const candidate = attrs2[key];
1516
+ if (candidate !== void 0 && candidate !== null && typeof candidate === "object") {
1517
+ return { present: true, value: candidate };
1518
+ }
1519
+ }
1520
+ if (event.inputSummary !== void 0 && event.inputSummary !== null && typeof event.inputSummary === "object") {
1521
+ return { present: true, value: event.inputSummary };
1522
+ }
1523
+ return { present: false, value: void 0 };
1524
+ }
1525
+ function evaluateToolArgumentValue(value, check, evidencePresent) {
1526
+ if (!evidencePresent) {
1527
+ return {
1528
+ status: "unavailable",
1529
+ code: "AI_CHECK_TOOL_ARGUMENT_EVIDENCE_UNAVAILABLE",
1530
+ message: `Structured argument evidence unavailable for tool ${check.tool} at ${check.path}.`
1531
+ };
1532
+ }
1533
+ const resolved = resolveJsonPointer(value, check.path);
1534
+ if (check.operator === "exists") {
1535
+ return resolved.found ? { status: "pass" } : {
1536
+ status: "fail",
1537
+ code: "AI_CHECK_TOOL_ARGUMENT_MISSING",
1538
+ message: `Expected path ${check.path} to exist on tool ${check.tool}.`
1539
+ };
1540
+ }
1541
+ if (!resolved.found) {
1542
+ return {
1543
+ status: "unavailable",
1544
+ code: "AI_CHECK_TOOL_ARGUMENT_EVIDENCE_UNAVAILABLE",
1545
+ message: `Path ${check.path} missing for tool ${check.tool}.`
1546
+ };
1547
+ }
1548
+ if (check.operator === "type") {
1549
+ const actual = jsonType(resolved.value);
1550
+ if (check.type === void 0) {
1551
+ return {
1552
+ status: "fail",
1553
+ code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1554
+ message: "type operator requires check.type"
1555
+ };
1556
+ }
1557
+ return actual === check.type ? { status: "pass" } : {
1558
+ status: "fail",
1559
+ code: "AI_CHECK_TOOL_ARGUMENT_TYPE",
1560
+ message: `Expected type ${check.type} at ${check.path} for tool ${check.tool}; found ${actual}.`
1561
+ };
1562
+ }
1563
+ if (check.operator === "equals") {
1564
+ return valuesEqual(resolved.value, check.expected) ? { status: "pass" } : {
1565
+ status: "fail",
1566
+ code: "AI_CHECK_TOOL_ARGUMENT_EQUALS",
1567
+ message: `Value at ${check.path} for tool ${check.tool} did not equal expected (bounded comparison).`
1568
+ };
1569
+ }
1570
+ if (check.operator === "oneOf") {
1571
+ const options = check.oneOf ?? [];
1572
+ if (options.length === 0) {
1573
+ return {
1574
+ status: "fail",
1575
+ code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1576
+ message: "oneOf operator requires a non-empty oneOf array"
1577
+ };
1578
+ }
1579
+ return options.some((option) => valuesEqual(resolved.value, option)) ? { status: "pass" } : {
1580
+ status: "fail",
1581
+ code: "AI_CHECK_TOOL_ARGUMENT_ONE_OF",
1582
+ message: `Value at ${check.path} for tool ${check.tool} was not in the allowed oneOf set.`
1583
+ };
1584
+ }
1585
+ return {
1586
+ status: "fail",
1587
+ code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1588
+ message: `Unsupported operator.`
1589
+ };
1590
+ }
1591
+
1465
1592
  // packages/core/src/checks/retry-safety.ts
1466
1593
  function fail2(ruleId, message, evidence, expected, actual) {
1467
1594
  return {
@@ -1777,131 +1904,366 @@ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
1777
1904
  return findings;
1778
1905
  }
1779
1906
 
1780
- // packages/core/src/checks/tool-arguments.ts
1781
- function decodeToken(token) {
1782
- return token.replace(/~1/g, "/").replace(/~0/g, "~");
1907
+ // packages/core/src/checks/recovery-operations.ts
1908
+ function fail3(ruleId, message, evidence, expected, actual) {
1909
+ return {
1910
+ ruleId,
1911
+ severity: "error",
1912
+ status: "fail",
1913
+ message,
1914
+ ...expected !== void 0 ? { expected } : {},
1915
+ ...actual !== void 0 ? { actual } : {},
1916
+ evidence: [...evidence]
1917
+ };
1783
1918
  }
1784
- function resolveJsonPointer(document, pointer) {
1785
- if (pointer === "") {
1786
- return { found: true, value: document };
1919
+ function workflowFor2(event) {
1920
+ const attrs2 = event.attributes;
1921
+ if (!attrs2 || typeof attrs2 !== "object") return {};
1922
+ const direct = extractSessionWorkflowMetadata(attrs2);
1923
+ const nested = attrs2.metadata && typeof attrs2.metadata === "object" ? extractSessionWorkflowMetadata(attrs2.metadata) : void 0;
1924
+ return { ...nested, ...direct };
1925
+ }
1926
+ function eventEvidence2(event) {
1927
+ return {
1928
+ runId: event.runId,
1929
+ eventId: event.eventId,
1930
+ kind: event.kind,
1931
+ name: event.name,
1932
+ status: event.status
1933
+ };
1934
+ }
1935
+ function eventTime2(event) {
1936
+ return event.startedAt ?? event.timestamp ?? "";
1937
+ }
1938
+ function sortByTime2(events) {
1939
+ return [...events].sort((a, b) => eventTime2(a).localeCompare(eventTime2(b)));
1940
+ }
1941
+ function hasIdempotencyEvidence2(event) {
1942
+ const workflow = workflowFor2(event);
1943
+ if (typeof workflow.idempotencyKey === "string" && workflow.idempotencyKey.trim() !== "") {
1944
+ return true;
1787
1945
  }
1788
- if (!pointer.startsWith("/")) {
1789
- return { found: false };
1946
+ const attrs2 = event.attributes ?? {};
1947
+ if (attrs2.noSideEffect === true) return true;
1948
+ if (attrs2.sideEffect === false) return true;
1949
+ return false;
1950
+ }
1951
+ function errorCodeOf(event) {
1952
+ if (typeof event.error?.code === "string" && event.error.code.trim() !== "") {
1953
+ return event.error.code.trim();
1790
1954
  }
1791
- const tokens = pointer.slice(1).split("/").map(decodeToken);
1792
- let current = document;
1793
- for (const token of tokens) {
1794
- if (current === null || current === void 0) {
1795
- return { found: false };
1796
- }
1797
- if (Array.isArray(current)) {
1798
- if (!/^(0|[1-9][0-9]*)$/.test(token)) {
1799
- return { found: false };
1800
- }
1801
- const index = Number(token);
1802
- if (index >= current.length) {
1803
- return { found: false };
1804
- }
1805
- current = current[index];
1806
- continue;
1807
- }
1808
- if (typeof current !== "object") {
1809
- return { found: false };
1810
- }
1811
- const record = current;
1812
- if (!Object.prototype.hasOwnProperty.call(record, token)) {
1813
- return { found: false };
1814
- }
1815
- current = record[token];
1955
+ const attrs2 = event.attributes ?? {};
1956
+ for (const key of ["errorCode", "code"]) {
1957
+ const value = attrs2[key];
1958
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
1816
1959
  }
1817
- return { found: true, value: current };
1818
- }
1819
- function jsonType(value) {
1820
- if (value === null) return "null";
1821
- if (Array.isArray(value)) return "array";
1822
- return typeof value;
1823
- }
1824
- function valuesEqual(left, right) {
1825
- return JSON.stringify(left) === JSON.stringify(right);
1960
+ const nested = attrs2.error;
1961
+ if (nested && typeof nested === "object") {
1962
+ const code = nested.code;
1963
+ if (typeof code === "string" && code.trim() !== "") return code.trim();
1964
+ }
1965
+ return void 0;
1826
1966
  }
1827
- function extractToolArgumentPayload(event) {
1967
+ function argumentDigestOf(event) {
1828
1968
  const attrs2 = event.attributes ?? {};
1829
- for (const key of ["arguments", "input", "toolArguments"]) {
1830
- const candidate = attrs2[key];
1831
- if (candidate !== void 0 && candidate !== null && typeof candidate === "object") {
1832
- return { present: true, value: candidate };
1833
- }
1969
+ for (const key of ["argumentsDigest", "inputDigest", "toolArgumentsDigest"]) {
1970
+ const value = attrs2[key];
1971
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
1834
1972
  }
1835
- if (event.inputSummary !== void 0 && event.inputSummary !== null && typeof event.inputSummary === "object") {
1836
- return { present: true, value: event.inputSummary };
1973
+ const commitment = attrs2.omittedPayloadCommitment;
1974
+ if (commitment && typeof commitment === "object") {
1975
+ const digest = commitment.digest;
1976
+ if (typeof digest === "string" && digest.trim() !== "") return digest.trim();
1837
1977
  }
1838
- return { present: false, value: void 0 };
1978
+ return void 0;
1839
1979
  }
1840
- function evaluateToolArgumentValue(value, check, evidencePresent) {
1841
- if (!evidencePresent) {
1980
+ function argumentFingerprint(event) {
1981
+ const payload = extractToolArgumentPayload(event);
1982
+ if (payload.present) {
1983
+ try {
1984
+ return { kind: "structured", value: JSON.stringify(payload.value) };
1985
+ } catch {
1986
+ return { kind: "missing" };
1987
+ }
1988
+ }
1989
+ const digest = argumentDigestOf(event);
1990
+ if (digest) return { kind: "digest", value: digest };
1991
+ return { kind: "missing" };
1992
+ }
1993
+ function sameArguments(left, right) {
1994
+ const a = argumentFingerprint(left);
1995
+ const b = argumentFingerprint(right);
1996
+ if (a.kind === "missing" || b.kind === "missing") {
1842
1997
  return {
1843
- status: "unavailable",
1844
- code: "AI_CHECK_TOOL_ARGUMENT_EVIDENCE_UNAVAILABLE",
1845
- message: `Structured argument evidence unavailable for tool ${check.tool} at ${check.path}.`
1998
+ ok: false,
1999
+ reason: "Structured or digest argument evidence unavailable for same-arguments check.",
2000
+ code: "AI_CHECK_RECOVERY_ARGUMENT_EVIDENCE_UNAVAILABLE"
1846
2001
  };
1847
2002
  }
1848
- const resolved = resolveJsonPointer(value, check.path);
1849
- if (check.operator === "exists") {
1850
- return resolved.found ? { status: "pass" } : {
1851
- status: "fail",
1852
- code: "AI_CHECK_TOOL_ARGUMENT_MISSING",
1853
- message: `Expected path ${check.path} to exist on tool ${check.tool}.`
2003
+ if (a.kind !== b.kind) {
2004
+ return {
2005
+ ok: false,
2006
+ reason: "Argument evidence kinds differ across attempts (structured vs digest).",
2007
+ code: "AI_CHECK_RECOVERY_ARGUMENT_EVIDENCE_MISMATCH"
1854
2008
  };
1855
2009
  }
1856
- if (!resolved.found) {
2010
+ if (a.value !== b.value) {
1857
2011
  return {
1858
- status: "unavailable",
1859
- code: "AI_CHECK_TOOL_ARGUMENT_EVIDENCE_UNAVAILABLE",
1860
- message: `Path ${check.path} missing for tool ${check.tool}.`
2012
+ ok: false,
2013
+ reason: "Retry arguments do not match prior attempt.",
2014
+ code: "AI_CHECK_RECOVERY_ARGUMENTS_DIFFER"
1861
2015
  };
1862
2016
  }
1863
- if (check.operator === "type") {
1864
- const actual = jsonType(resolved.value);
1865
- if (check.type === void 0) {
1866
- return {
1867
- status: "fail",
1868
- code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1869
- message: "type operator requires check.type"
1870
- };
2017
+ return { ok: true };
2018
+ }
2019
+ function isUnevaluableWriteCompletion(event) {
2020
+ if (event.status === "running" || event.status === "unknown") return true;
2021
+ const attrs2 = event.attributes ?? {};
2022
+ if (attrs2.timeout === true) return true;
2023
+ if (attrs2.completionState === "unknown" || attrs2.completionState === "timeout") return true;
2024
+ const code = errorCodeOf(event)?.toUpperCase();
2025
+ if (code === "TIMEOUT" || code === "UNKNOWN_COMPLETION" || code === "DEADLINE_EXCEEDED") {
2026
+ return true;
2027
+ }
2028
+ return false;
2029
+ }
2030
+ function collectExplicitReferences(event) {
2031
+ const refs = /* @__PURE__ */ new Set();
2032
+ const attrs2 = event.attributes ?? {};
2033
+ const candidates = [
2034
+ attrs2.referencedEventId,
2035
+ attrs2.referencedEventIds,
2036
+ attrs2.toolResultEventId,
2037
+ attrs2.toolResultEventIds,
2038
+ attrs2.inputEventIds,
2039
+ attrs2.dependsOnEventIds,
2040
+ attrs2.evidence,
2041
+ workflowFor2(event).toolCallId
2042
+ ];
2043
+ for (const candidate of candidates) {
2044
+ if (typeof candidate === "string" && candidate.trim() !== "") {
2045
+ refs.add(candidate.trim());
2046
+ continue;
2047
+ }
2048
+ if (Array.isArray(candidate)) {
2049
+ for (const item of candidate) {
2050
+ if (typeof item === "string" && item.trim() !== "") refs.add(item.trim());
2051
+ if (item && typeof item === "object") {
2052
+ const id = item.eventId;
2053
+ if (typeof id === "string" && id.trim() !== "") refs.add(id.trim());
2054
+ }
2055
+ }
2056
+ continue;
2057
+ }
2058
+ if (candidate && typeof candidate === "object") {
2059
+ const obj = candidate;
2060
+ if (typeof obj.eventId === "string" && obj.eventId.trim() !== "") {
2061
+ refs.add(obj.eventId.trim());
2062
+ }
2063
+ if (Array.isArray(obj.eventIds)) {
2064
+ for (const id of obj.eventIds) {
2065
+ if (typeof id === "string" && id.trim() !== "") refs.add(id.trim());
2066
+ }
2067
+ }
1871
2068
  }
1872
- return actual === check.type ? { status: "pass" } : {
1873
- status: "fail",
1874
- code: "AI_CHECK_TOOL_ARGUMENT_TYPE",
1875
- message: `Expected type ${check.type} at ${check.path} for tool ${check.tool}; found ${actual}.`
1876
- };
1877
2069
  }
1878
- if (check.operator === "equals") {
1879
- return valuesEqual(resolved.value, check.expected) ? { status: "pass" } : {
1880
- status: "fail",
1881
- code: "AI_CHECK_TOOL_ARGUMENT_EQUALS",
1882
- message: `Value at ${check.path} for tool ${check.tool} did not equal expected (bounded comparison).`
1883
- };
2070
+ return refs;
2071
+ }
2072
+ function llmReferencesTool(llm, toolEvent, requireExplicit) {
2073
+ if (!requireExplicit) {
2074
+ return eventTime2(llm) >= eventTime2(toolEvent);
1884
2075
  }
1885
- if (check.operator === "oneOf") {
1886
- const options = check.oneOf ?? [];
1887
- if (options.length === 0) {
1888
- return {
1889
- status: "fail",
1890
- code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1891
- message: "oneOf operator requires a non-empty oneOf array"
1892
- };
2076
+ const refs = collectExplicitReferences(llm);
2077
+ if (refs.has(toolEvent.eventId)) return true;
2078
+ const toolCallId = workflowFor2(toolEvent).toolCallId;
2079
+ if (toolCallId && refs.has(toolCallId)) return true;
2080
+ return false;
2081
+ }
2082
+ function groupToolAttempts(events, toolName2) {
2083
+ const groups = /* @__PURE__ */ new Map();
2084
+ for (const event of events) {
2085
+ if (event.kind !== "TOOL") continue;
2086
+ if (resolveCanonicalToolName(event) !== toolName2) continue;
2087
+ const workflow = workflowFor2(event);
2088
+ const key = workflow.operationId ?? `__tool__:${toolName2}`;
2089
+ const list = groups.get(key) ?? [];
2090
+ list.push(event);
2091
+ groups.set(key, list);
2092
+ }
2093
+ return groups;
2094
+ }
2095
+ function evaluateRecoveryOperations(events, operations) {
2096
+ const findings = [];
2097
+ const llmEvents = events.filter((event) => event.kind === "LLM");
2098
+ for (const operation of operations) {
2099
+ const sideEffectClass = operation.sideEffectClass ?? "read";
2100
+ const groups = groupToolAttempts(events, operation.tool);
2101
+ if (groups.size === 0) {
2102
+ if (operation.requireTerminalSuccess) {
2103
+ findings.push(
2104
+ fail3(
2105
+ "contract.retry.operations.terminal-success",
2106
+ `Recovery operation for tool ${operation.tool} requires a terminal success but no attempts were observed.`,
2107
+ [],
2108
+ "ok",
2109
+ "missing"
2110
+ )
2111
+ );
2112
+ }
2113
+ continue;
2114
+ }
2115
+ for (const [operationKey, members] of groups) {
2116
+ const ordered = sortByTime2(members);
2117
+ if (operation.maxAttempts !== void 0) {
2118
+ const count = countOperationAttempts(members);
2119
+ if (count > operation.maxAttempts) {
2120
+ findings.push(
2121
+ fail3(
2122
+ "contract.retry.operations.max-attempts",
2123
+ `Tool ${operation.tool} operation ${operationKey} exceeded maxAttempts ${operation.maxAttempts}.`,
2124
+ members.slice(0, 4).map(eventEvidence2),
2125
+ operation.maxAttempts,
2126
+ count
2127
+ )
2128
+ );
2129
+ }
2130
+ }
2131
+ if (sideEffectClass === "write") {
2132
+ for (const event of ordered) {
2133
+ if (isUnevaluableWriteCompletion(event) && !hasIdempotencyEvidence2(event)) {
2134
+ findings.push(
2135
+ fail3(
2136
+ "contract.retry.operations.write-completion-unevaluable",
2137
+ `Write tool ${operation.tool} has timeout/unknown completion without authoritative idempotency evidence.`,
2138
+ [eventEvidence2(event)],
2139
+ "idempotencyKey|authoritative completion",
2140
+ {
2141
+ code: "AI_CHECK_RECOVERY_WRITE_COMPLETION_UNAVAILABLE",
2142
+ status: event.status
2143
+ }
2144
+ )
2145
+ );
2146
+ }
2147
+ }
2148
+ }
2149
+ const prior = [];
2150
+ let sawEarlierError = false;
2151
+ let sawLaterOkAfterError = false;
2152
+ let lastOk;
2153
+ for (const event of ordered) {
2154
+ const classification = classifyRetryAttempt(event, prior, members);
2155
+ const isRetry = classification.kind === "retry";
2156
+ if (isRetry && operation.requireFailureBeforeRetry) {
2157
+ const earlierFailure = prior.some(
2158
+ (candidate) => candidate.status === "error" && eventTime2(candidate) !== "" && eventTime2(event) !== "" && eventTime2(candidate) < eventTime2(event)
2159
+ );
2160
+ if (!earlierFailure) {
2161
+ findings.push(
2162
+ fail3(
2163
+ "contract.retry.operations.failure-before-retry",
2164
+ `Retry of tool ${operation.tool} appeared without an earlier failure in the same operation.`,
2165
+ [eventEvidence2(event)],
2166
+ "earlier error attempt",
2167
+ { operationKey }
2168
+ )
2169
+ );
2170
+ }
2171
+ }
2172
+ if (isRetry && operation.retryableErrors?.codes && operation.retryableErrors.codes.length > 0) {
2173
+ const allowed = new Set(operation.retryableErrors.codes);
2174
+ const priorErrors = prior.filter((candidate) => candidate.status === "error");
2175
+ const lastError = priorErrors[priorErrors.length - 1];
2176
+ if (lastError) {
2177
+ const code = errorCodeOf(lastError);
2178
+ if (!code || !allowed.has(code)) {
2179
+ findings.push(
2180
+ fail3(
2181
+ "contract.retry.operations.retryable-error",
2182
+ `Retry of tool ${operation.tool} followed a non-retryable or missing error code.`,
2183
+ [eventEvidence2(event), eventEvidence2(lastError)],
2184
+ [...allowed],
2185
+ code ?? null
2186
+ )
2187
+ );
2188
+ }
2189
+ }
2190
+ }
2191
+ if (isRetry && (operation.requireSameArguments === true || operation.requireSameArguments === "structured-or-digest")) {
2192
+ const baseline = prior[0] ?? prior[prior.length - 1];
2193
+ if (baseline) {
2194
+ const comparison = sameArguments(baseline, event);
2195
+ if (!comparison.ok) {
2196
+ findings.push(
2197
+ fail3(
2198
+ "contract.retry.operations.same-arguments",
2199
+ comparison.reason,
2200
+ [eventEvidence2(baseline), eventEvidence2(event)],
2201
+ "matching structured args or digests",
2202
+ { code: comparison.code }
2203
+ )
2204
+ );
2205
+ }
2206
+ }
2207
+ }
2208
+ if (event.status === "error") {
2209
+ sawEarlierError = true;
2210
+ } else if (event.status === "ok" && sawEarlierError) {
2211
+ sawLaterOkAfterError = true;
2212
+ }
2213
+ if (event.status === "ok") {
2214
+ lastOk = event;
2215
+ }
2216
+ prior.push(event);
2217
+ }
2218
+ if (operation.requireTerminalSuccess) {
2219
+ const hasOk = ordered.some((event) => event.status === "ok");
2220
+ if (!hasOk) {
2221
+ findings.push(
2222
+ fail3(
2223
+ "contract.retry.operations.terminal-success",
2224
+ `Tool ${operation.tool} operation ${operationKey} has no terminal ok success.`,
2225
+ ordered.slice(0, 4).map(eventEvidence2),
2226
+ "ok",
2227
+ ordered.map((event) => event.status)
2228
+ )
2229
+ );
2230
+ }
2231
+ }
2232
+ if (operation.requireRecoveredFailureVisible) {
2233
+ const hasOk = ordered.some((event) => event.status === "ok");
2234
+ if (ordered.length > 1 && hasOk && !sawLaterOkAfterError) {
2235
+ findings.push(
2236
+ fail3(
2237
+ "contract.retry.operations.recovered-failure-visible",
2238
+ `Tool ${operation.tool} operation ${operationKey} does not show an earlier error before later success.`,
2239
+ ordered.slice(0, 4).map(eventEvidence2),
2240
+ "earlier error then later ok",
2241
+ { sawEarlierError }
2242
+ )
2243
+ );
2244
+ }
2245
+ }
2246
+ const dependency = operation.successfulResultDependency;
2247
+ if (dependency?.consumerKind === "LLM" && lastOk) {
2248
+ const requireExplicit = dependency.requireExplicitReference === true;
2249
+ const referenced = llmEvents.some(
2250
+ (llmEvent) => llmReferencesTool(llmEvent, lastOk, requireExplicit)
2251
+ );
2252
+ if (!referenced) {
2253
+ findings.push(
2254
+ fail3(
2255
+ "contract.retry.operations.successful-result-dependency",
2256
+ requireExplicit ? `Successful ${operation.tool} result is not explicitly referenced by a later LLM event.` : `Successful ${operation.tool} result has no later LLM consumer.`,
2257
+ [eventEvidence2(lastOk)],
2258
+ "LLM reference to successful tool event",
2259
+ { code: "AI_CHECK_RECOVERY_RESULT_DEPENDENCY_MISSING" }
2260
+ )
2261
+ );
2262
+ }
2263
+ }
1893
2264
  }
1894
- return options.some((option) => valuesEqual(resolved.value, option)) ? { status: "pass" } : {
1895
- status: "fail",
1896
- code: "AI_CHECK_TOOL_ARGUMENT_ONE_OF",
1897
- message: `Value at ${check.path} for tool ${check.tool} was not in the allowed oneOf set.`
1898
- };
1899
2265
  }
1900
- return {
1901
- status: "fail",
1902
- code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1903
- message: `Unsupported operator.`
1904
- };
2266
+ return findings;
1905
2267
  }
1906
2268
 
1907
2269
  // packages/core/src/checks/contract.ts
@@ -1950,7 +2312,23 @@ function cloneBody(body) {
1950
2312
  ...body.retry ? {
1951
2313
  retry: {
1952
2314
  ...body.retry,
1953
- ...body.retry.nonIdempotentTools ? { nonIdempotentTools: [...body.retry.nonIdempotentTools] } : {}
2315
+ ...body.retry.nonIdempotentTools ? { nonIdempotentTools: [...body.retry.nonIdempotentTools] } : {},
2316
+ ...body.retry.operations ? {
2317
+ operations: body.retry.operations.map((operation) => ({
2318
+ ...operation,
2319
+ ...operation.retryableErrors ? {
2320
+ retryableErrors: {
2321
+ ...operation.retryableErrors,
2322
+ ...operation.retryableErrors.codes ? { codes: [...operation.retryableErrors.codes] } : {}
2323
+ }
2324
+ } : {},
2325
+ ...operation.successfulResultDependency ? {
2326
+ successfulResultDependency: {
2327
+ ...operation.successfulResultDependency
2328
+ }
2329
+ } : {}
2330
+ }))
2331
+ } : {}
1954
2332
  }
1955
2333
  } : {}
1956
2334
  };
@@ -2385,7 +2763,11 @@ function contractToRules(contract) {
2385
2763
  name: context.selectedRun.name
2386
2764
  }
2387
2765
  ] : [];
2388
- return evaluateRetrySafetyRules(events, retry);
2766
+ const base = evaluateRetrySafetyRules(events, retry);
2767
+ if (!retry.operations || retry.operations.length === 0) {
2768
+ return base;
2769
+ }
2770
+ return [...base, ...evaluateRecoveryOperations(events, retry.operations)];
2389
2771
  }
2390
2772
  });
2391
2773
  }
@@ -2870,7 +3252,10 @@ function explainTraceContract(contract) {
2870
3252
  lines.push("Base: declared-versus-enforced control checks enabled.");
2871
3253
  }
2872
3254
  if (contract.retry) {
2873
- lines.push("Base: retry/side-effect safety checks enabled.");
3255
+ const opCount = contract.retry.operations?.length ?? 0;
3256
+ lines.push(
3257
+ opCount > 0 ? `Base: retry/side-effect safety checks enabled (${opCount} recovery operation oracle(s)).` : "Base: retry/side-effect safety checks enabled."
3258
+ );
2874
3259
  }
2875
3260
  const branches = contract.alternatives?.anyOf ?? [];
2876
3261
  if (branches.length > 0) {
@@ -3221,7 +3606,7 @@ function stripPrefix(name, prefixes) {
3221
3606
  }
3222
3607
  return name;
3223
3608
  }
3224
- function eventEvidence2(event, path3) {
3609
+ function eventEvidence3(event, path3) {
3225
3610
  return {
3226
3611
  runId: event.runId,
3227
3612
  eventId: event.eventId,
@@ -3491,7 +3876,7 @@ function guardrailShape(context) {
3491
3876
  }
3492
3877
  function firstEvidenceForKind(context, kind, path3) {
3493
3878
  const event = semanticEvents(context).find((candidate) => candidate.kind === kind);
3494
- return event ? [eventEvidence2(event, path3)] : runEvidence(context.selectedRun);
3879
+ return event ? [eventEvidence3(event, path3)] : runEvidence(context.selectedRun);
3495
3880
  }
3496
3881
  function baselineDiffFinding(message, evidence, expected, actual) {
3497
3882
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -3524,7 +3909,7 @@ function createRunStatusRule(options = {}) {
3524
3909
  failFinding(
3525
3910
  "run.status",
3526
3911
  "Run contains incomplete running events.",
3527
- running.map((event) => eventEvidence2(event)),
3912
+ running.map((event) => eventEvidence3(event)),
3528
3913
  "no running events",
3529
3914
  running.length
3530
3915
  )
@@ -3570,7 +3955,7 @@ function createMaxStepDurationRule(options) {
3570
3955
  failFinding(
3571
3956
  "run.maxStepDuration",
3572
3957
  `${over.length} step(s) exceeded max duration ${options.maxDurationMs}ms.`,
3573
- over.map((event) => eventEvidence2(event, "durationMs")),
3958
+ over.map((event) => eventEvidence3(event, "durationMs")),
3574
3959
  { maxDurationMs: options.maxDurationMs },
3575
3960
  over.map((event) => ({
3576
3961
  eventId: event.eventId,
@@ -3596,7 +3981,7 @@ function createStallDetectionRule(options = {}) {
3596
3981
  failFinding(
3597
3982
  "run.stall",
3598
3983
  `Found ${running.length} event(s) still running (possible stall).`,
3599
- running.map((event) => eventEvidence2(event, "status")),
3984
+ running.map((event) => eventEvidence3(event, "status")),
3600
3985
  "no running events",
3601
3986
  running.length
3602
3987
  )
@@ -3611,7 +3996,7 @@ function createStallDetectionRule(options = {}) {
3611
3996
  failFinding(
3612
3997
  "run.stall",
3613
3998
  `Found ${incomplete.length} started event(s) without endedAt.`,
3614
- incomplete.map((event) => eventEvidence2(event, "endedAt")),
3999
+ incomplete.map((event) => eventEvidence3(event, "endedAt")),
3615
4000
  "endedAt for started events",
3616
4001
  incomplete.length
3617
4002
  )
@@ -3647,7 +4032,7 @@ function createRequireCompletedRule() {
3647
4032
  failFinding(
3648
4033
  "run.requireCompleted",
3649
4034
  `Run has ${running.length} incomplete running event(s).`,
3650
- running.map((event) => eventEvidence2(event, "status")),
4035
+ running.map((event) => eventEvidence3(event, "status")),
3651
4036
  "no running events",
3652
4037
  running.length
3653
4038
  )
@@ -3745,12 +4130,12 @@ function createToolUsageRule(options) {
3745
4130
  const name = toolName(event);
3746
4131
  if (forbidden.has(name)) {
3747
4132
  findings.push(
3748
- failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence2(event)], "tool absent", name)
4133
+ failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence3(event)], "tool absent", name)
3749
4134
  );
3750
4135
  }
3751
4136
  if (allowed && !allowed.has(name)) {
3752
4137
  findings.push(
3753
- failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence2(event)], [...allowed].sort(), name)
4138
+ failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence3(event)], [...allowed].sort(), name)
3754
4139
  );
3755
4140
  }
3756
4141
  }
@@ -3761,7 +4146,7 @@ function createToolUsageRule(options) {
3761
4146
  }
3762
4147
  if (options.maxCount !== void 0 && tools.length > options.maxCount) {
3763
4148
  findings.push(
3764
- failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence2(event)), { maxCount: options.maxCount }, tools.length)
4149
+ failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence3(event)), { maxCount: options.maxCount }, tools.length)
3765
4150
  );
3766
4151
  }
3767
4152
  return findings;
@@ -3788,7 +4173,7 @@ function createToolOrderingRule(options) {
3788
4173
  failFinding(
3789
4174
  ruleId,
3790
4175
  `Tool ${options.before} must appear before ${options.after}.`,
3791
- [eventEvidence2(tools[beforeIndex]), eventEvidence2(tools[afterIndex])],
4176
+ [eventEvidence3(tools[beforeIndex]), eventEvidence3(tools[afterIndex])],
3792
4177
  { before: options.before, after: options.after },
3793
4178
  tools.map(toolName)
3794
4179
  )
@@ -3810,7 +4195,7 @@ function createToolOrderingRule(options) {
3810
4195
  failFinding(
3811
4196
  ruleId,
3812
4197
  `Tool ${options.before} cannot causally happen before itself.`,
3813
- [eventEvidence2(beforeEvent)],
4198
+ [eventEvidence3(beforeEvent)],
3814
4199
  expected,
3815
4200
  { code: "tool.order.same-tool" }
3816
4201
  )
@@ -3821,7 +4206,7 @@ function createToolOrderingRule(options) {
3821
4206
  failFinding(
3822
4207
  ruleId,
3823
4208
  `Tool order ${options.before} before ${options.after} could not establish causal timing.`,
3824
- [eventEvidence2(beforeEvent), eventEvidence2(afterEvent)],
4209
+ [eventEvidence3(beforeEvent), eventEvidence3(afterEvent)],
3825
4210
  expected,
3826
4211
  {
3827
4212
  code: "tool.order.interval-unresolved",
@@ -3836,7 +4221,7 @@ function createToolOrderingRule(options) {
3836
4221
  failFinding(
3837
4222
  ruleId,
3838
4223
  `Tool ${options.before} must finish before ${options.after} starts.`,
3839
- [eventEvidence2(beforeEvent), eventEvidence2(afterEvent)],
4224
+ [eventEvidence3(beforeEvent), eventEvidence3(afterEvent)],
3840
4225
  expected,
3841
4226
  {
3842
4227
  beforeEndedAt: new Date(beforeEnd).toISOString(),
@@ -3861,7 +4246,7 @@ function createToolOrderingRule(options) {
3861
4246
  failFinding(
3862
4247
  ruleId,
3863
4248
  `Tool ${options.before} cannot causally happen before itself.`,
3864
- [eventEvidence2(beforeEvent)],
4249
+ [eventEvidence3(beforeEvent)],
3865
4250
  expected,
3866
4251
  { code: "tool.order.same-tool" }
3867
4252
  )
@@ -3896,7 +4281,7 @@ function createToolOrderingRule(options) {
3896
4281
  failFinding(
3897
4282
  ruleId,
3898
4283
  `Tool order ${options.before} before ${options.after} could not establish all causal intervals.`,
3899
- [firstMissingBefore, firstMissingAfter].filter((event) => event !== void 0).map((event) => eventEvidence2(event)),
4284
+ [firstMissingBefore, firstMissingAfter].filter((event) => event !== void 0).map((event) => eventEvidence3(event)),
3900
4285
  expected,
3901
4286
  {
3902
4287
  code: "tool.order.interval-unresolved",
@@ -3911,7 +4296,7 @@ function createToolOrderingRule(options) {
3911
4296
  failFinding(
3912
4297
  ruleId,
3913
4298
  `Every ${options.before} tool call must finish before any ${options.after} tool call starts.`,
3914
- [eventEvidence2(latestBefore.event), eventEvidence2(earliestAfter.event)],
4299
+ [eventEvidence3(latestBefore.event), eventEvidence3(earliestAfter.event)],
3915
4300
  expected,
3916
4301
  {
3917
4302
  latestBeforeEndedAt: new Date(latestBefore.end).toISOString(),
@@ -3935,7 +4320,7 @@ function createToolOrderingRule(options) {
3935
4320
  beforeEndedAt: beforeEvent.endedAt,
3936
4321
  afterStartedAt: afterEvent.startedAt ?? afterEvent.timestamp
3937
4322
  },
3938
- evidence: [eventEvidence2(beforeEvent), eventEvidence2(afterEvent)]
4323
+ evidence: [eventEvidence3(beforeEvent), eventEvidence3(afterEvent)]
3939
4324
  }
3940
4325
  ];
3941
4326
  }
@@ -3958,7 +4343,7 @@ function createToolFailureRule(options) {
3958
4343
  failFinding(
3959
4344
  "tool.failures",
3960
4345
  `Tool failure count ${failures.length} exceeded ${options.maxFailures}.`,
3961
- failures.map((event) => eventEvidence2(event)),
4346
+ failures.map((event) => eventEvidence3(event)),
3962
4347
  { maxFailures: options.maxFailures },
3963
4348
  failures.length
3964
4349
  )
@@ -3971,7 +4356,7 @@ function createToolFailureRule(options) {
3971
4356
  failFinding(
3972
4357
  "tool.failures",
3973
4358
  `Tool retry count exceeded ${options.maxRetries}.`,
3974
- excessiveRetries.map((item) => eventEvidence2(item.event, "attributes.retryCount")),
4359
+ excessiveRetries.map((item) => eventEvidence3(item.event, "attributes.retryCount")),
3975
4360
  { maxRetries: options.maxRetries },
3976
4361
  excessiveRetries.map((item) => ({ tool: toolName(item.event), retries: item.count }))
3977
4362
  )
@@ -3998,7 +4383,7 @@ function createLlmUsageRule(options) {
3998
4383
  failFinding(
3999
4384
  "llm.usage",
4000
4385
  `LLM call count ${llms.length} exceeded ${options.maxCalls}.`,
4001
- llms.map((event) => eventEvidence2(event)),
4386
+ llms.map((event) => eventEvidence3(event)),
4002
4387
  { maxCalls: options.maxCalls },
4003
4388
  llms.length
4004
4389
  )
@@ -4010,17 +4395,17 @@ function createLlmUsageRule(options) {
4010
4395
  const finishReason = llmFinishReason(event);
4011
4396
  if (allowedModels && (!model || !allowedModels.has(model))) {
4012
4397
  findings.push(
4013
- failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence2(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
4398
+ failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence3(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
4014
4399
  );
4015
4400
  }
4016
4401
  if (allowedProviders && (!provider || !allowedProviders.has(provider))) {
4017
4402
  findings.push(
4018
- failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence2(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
4403
+ failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence3(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
4019
4404
  );
4020
4405
  }
4021
4406
  if (finishReasons && (!finishReason || !finishReasons.has(finishReason))) {
4022
4407
  findings.push(
4023
- failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence2(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
4408
+ failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence3(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
4024
4409
  );
4025
4410
  }
4026
4411
  }
@@ -4045,7 +4430,7 @@ function createLlmUsageRule(options) {
4045
4430
  failFinding(
4046
4431
  "llm.usage",
4047
4432
  `LLM ${key} token count ${tokenTotals[key]} exceeded ${limit}.`,
4048
- llms.map((event) => eventEvidence2(event, `tokenUsage.${key}`)),
4433
+ llms.map((event) => eventEvidence3(event, `tokenUsage.${key}`)),
4049
4434
  { [`max${key[0].toUpperCase()}${key.slice(1)}Tokens`]: limit },
4050
4435
  tokenTotals[key]
4051
4436
  )
@@ -4070,7 +4455,7 @@ function createStructureIncompleteRule(options = {}) {
4070
4455
  failFinding(
4071
4456
  "structure.incomplete",
4072
4457
  "Trace contains incomplete running events.",
4073
- running.map((event) => eventEvidence2(event, "status")),
4458
+ running.map((event) => eventEvidence3(event, "status")),
4074
4459
  "no running events",
4075
4460
  running.length
4076
4461
  )
@@ -4086,7 +4471,7 @@ function createStructureIncompleteRule(options = {}) {
4086
4471
  failFinding(
4087
4472
  "structure.incomplete",
4088
4473
  "Trace contains events with startedAt but no endedAt.",
4089
- missingEndedAt.map((event) => eventEvidence2(event, "endedAt")),
4474
+ missingEndedAt.map((event) => eventEvidence3(event, "endedAt")),
4090
4475
  "endedAt for started events",
4091
4476
  missingEndedAt.length
4092
4477
  )
@@ -4114,7 +4499,7 @@ function createStructureOrphanRule(options = {}) {
4114
4499
  failFinding(
4115
4500
  "structure.orphan",
4116
4501
  "Trace contains events whose parentId is not present in the selected run.",
4117
- orphans.map((event) => eventEvidence2(event, "parentId")),
4502
+ orphans.map((event) => eventEvidence3(event, "parentId")),
4118
4503
  "parentId resolves to an event in the selected run",
4119
4504
  orphans.length
4120
4505
  )
@@ -4146,7 +4531,7 @@ function createStructureCycleRule() {
4146
4531
  failFinding(
4147
4532
  "structure.cycle",
4148
4533
  formatProgrammaticDiagnostic("AI_TRACE_RELATIONSHIP_CYCLE"),
4149
- cycle.map((item) => eventEvidence2(item, "parentId")),
4534
+ cycle.map((item) => eventEvidence3(item, "parentId")),
4150
4535
  "acyclic parentId graph",
4151
4536
  cycle.map((item) => item.eventId).sort()
4152
4537
  )
@@ -4178,7 +4563,7 @@ function createStructureRelationshipRule(options = {}) {
4178
4563
  failFinding(
4179
4564
  "structure.relationship",
4180
4565
  `Event confidence ${event.confidence} is below ${minConfidence}.`,
4181
- [eventEvidence2(event, "confidence")],
4566
+ [eventEvidence3(event, "confidence")],
4182
4567
  { minConfidence },
4183
4568
  event.confidence
4184
4569
  )
@@ -4190,7 +4575,7 @@ function createStructureRelationshipRule(options = {}) {
4190
4575
  failFinding(
4191
4576
  "structure.relationship",
4192
4577
  "Event parentId points to itself.",
4193
- [eventEvidence2(event, "parentId")],
4578
+ [eventEvidence3(event, "parentId")],
4194
4579
  "parentId references a distinct event",
4195
4580
  "self"
4196
4581
  )
@@ -4207,7 +4592,7 @@ function createStructureRelationshipRule(options = {}) {
4207
4592
  failFinding(
4208
4593
  "structure.relationship",
4209
4594
  "Parent event starts after child event.",
4210
- [eventEvidence2(parent), eventEvidence2(event, "parentId")],
4595
+ [eventEvidence3(parent), eventEvidence3(event, "parentId")],
4211
4596
  "parent start <= child start",
4212
4597
  { parentEventId: parent.eventId, childEventId: event.eventId }
4213
4598
  )
@@ -4221,7 +4606,7 @@ function createStructureRelationshipRule(options = {}) {
4221
4606
  failFinding(
4222
4607
  "structure.relationship",
4223
4608
  "Trace parentSpanId does not match parent spanId.",
4224
- [eventEvidence2(event, "trace.parentSpanId")],
4609
+ [eventEvidence3(event, "trace.parentSpanId")],
4225
4610
  { parentSpanId: parent.trace.spanId },
4226
4611
  actual ?? "missing"
4227
4612
  )
@@ -4250,7 +4635,7 @@ function createStructureParallelWidthRule(options) {
4250
4635
  "structure.parallelWidth",
4251
4636
  `Parent ${parentId} has ${children.length} children, exceeding ${options.maxChildren}.`,
4252
4637
  [
4253
- ...parent ? [eventEvidence2(parent)] : [{ runId: context.selectedRun?.runId, eventId: parentId }],
4638
+ ...parent ? [eventEvidence3(parent)] : [{ runId: context.selectedRun?.runId, eventId: parentId }],
4254
4639
  ...children.map((child) => ({
4255
4640
  runId: child.event.runId,
4256
4641
  eventId: child.event.eventId,
@@ -4298,7 +4683,7 @@ function createStructureParallelWidthRule(options) {
4298
4683
  failFinding(
4299
4684
  "structure.parallelWidth",
4300
4685
  `Concurrent event width ${maxActive.length} exceeded ${options.maxConcurrent}.`,
4301
- maxActive.map((event) => eventEvidence2(event)),
4686
+ maxActive.map((event) => eventEvidence3(event)),
4302
4687
  { maxConcurrent: options.maxConcurrent },
4303
4688
  maxActive.length
4304
4689
  )
@@ -4341,7 +4726,7 @@ function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
4341
4726
  failFinding(
4342
4727
  ruleId,
4343
4728
  `Forbidden ${label} ${name} appeared.`,
4344
- [eventEvidence2(event)],
4729
+ [eventEvidence3(event)],
4345
4730
  `${label} absent`,
4346
4731
  name
4347
4732
  )
@@ -4352,7 +4737,7 @@ function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
4352
4737
  failFinding(
4353
4738
  ruleId,
4354
4739
  `${label[0].toUpperCase()}${label.slice(1)} ${name} is not in the allowed set.`,
4355
- [eventEvidence2(event)],
4740
+ [eventEvidence3(event)],
4356
4741
  [...allowed].sort(),
4357
4742
  name
4358
4743
  )
@@ -4375,7 +4760,7 @@ function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
4375
4760
  failFinding(
4376
4761
  ruleId,
4377
4762
  `${label[0].toUpperCase()}${label.slice(1)} count ${events.length} exceeded maximum ${options.maxCount}.`,
4378
- events.map((event) => eventEvidence2(event)),
4763
+ events.map((event) => eventEvidence3(event)),
4379
4764
  { maxCount: options.maxCount },
4380
4765
  events.length
4381
4766
  )
@@ -4429,7 +4814,7 @@ function createSafetyRedactionRule(options = {}) {
4429
4814
  failFinding(
4430
4815
  "safety.redaction",
4431
4816
  `Sensitive-looking field at ${entry.path} is not redacted.`,
4432
- [eventEvidence2(event, entry.path)],
4817
+ [eventEvidence3(event, entry.path)],
4433
4818
  "redaction marker",
4434
4819
  { path: entry.path, valueType: valueType(entry.value) },
4435
4820
  {
@@ -4463,7 +4848,7 @@ function createSafetyRawContentRule(options = {}) {
4463
4848
  failFinding(
4464
4849
  "safety.rawPrompt",
4465
4850
  `Raw content-like field ${entry.path} is present.`,
4466
- [eventEvidence2(event, entry.path)],
4851
+ [eventEvidence3(event, entry.path)],
4467
4852
  "metadata-only trace fields",
4468
4853
  { path: entry.path, valueType: valueType(entry.value) },
4469
4854
  {
@@ -4501,7 +4886,7 @@ function createSafetySecretPatternRule(options = {}) {
4501
4886
  failFinding(
4502
4887
  "safety.secretPattern",
4503
4888
  `Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
4504
- [eventEvidence2(event, entry.path)],
4889
+ [eventEvidence3(event, entry.path)],
4505
4890
  "no secret-like strings",
4506
4891
  { pattern: pattern.id, path: entry.path },
4507
4892
  {
@@ -4534,7 +4919,7 @@ function createSafetyOversizedAttributeRule(options) {
4534
4919
  failFinding(
4535
4920
  "safety.oversizedAttribute",
4536
4921
  `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
4537
- [eventEvidence2(event, entry.path)],
4922
+ [eventEvidence3(event, entry.path)],
4538
4923
  { maxStringLength: options.maxStringLength },
4539
4924
  { path: entry.path, length: entry.value.length },
4540
4925
  {
@@ -4551,7 +4936,7 @@ function createSafetyOversizedAttributeRule(options) {
4551
4936
  failFinding(
4552
4937
  "safety.oversizedAttribute",
4553
4938
  `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
4554
- [eventEvidence2(event, entry.path)],
4939
+ [eventEvidence3(event, entry.path)],
4555
4940
  { maxArrayLength: options.maxArrayLength },
4556
4941
  { path: entry.path, length: entry.value.length },
4557
4942
  {
@@ -4568,7 +4953,7 @@ function createSafetyOversizedAttributeRule(options) {
4568
4953
  failFinding(
4569
4954
  "safety.oversizedAttribute",
4570
4955
  `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
4571
- [eventEvidence2(event, entry.path)],
4956
+ [eventEvidence3(event, entry.path)],
4572
4957
  { maxObjectKeys: options.maxObjectKeys },
4573
4958
  { path: entry.path, keys: Object.keys(entry.value).length },
4574
4959
  {
@@ -4587,7 +4972,7 @@ function createSafetyOversizedAttributeRule(options) {
4587
4972
  failFinding(
4588
4973
  "safety.oversizedAttribute",
4589
4974
  `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
4590
- [eventEvidence2(event, entry.path)],
4975
+ [eventEvidence3(event, entry.path)],
4591
4976
  { maxSerializedBytes: options.maxSerializedBytes },
4592
4977
  { path: entry.path, bytes },
4593
4978
  {
@@ -4714,7 +5099,7 @@ function createBaselineRegressionRule(options) {
4714
5099
  path: "guardrail",
4715
5100
  expected: guardrailShape(baselineContext),
4716
5101
  actual: guardrailShape(context),
4717
- evidence: guardrailEvents(context)[0] ? [eventEvidence2(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
5102
+ evidence: guardrailEvents(context)[0] ? [eventEvidence3(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
4718
5103
  }
4719
5104
  ];
4720
5105
  for (const comparison of comparisons) {