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.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/docs/BUNDLES.md +4 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/SAFE-TRACE-SHARING.md +2 -0
- package/docs/TRACE-CONTRACTS.md +21 -3
- package/package.json +1 -1
- package/packages/cli/dist/{chunk-HPR2OJPU.mjs → chunk-JVQ4LTHR.mjs} +4317 -3803
- package/packages/cli/dist/chunk-JVQ4LTHR.mjs.map +1 -0
- package/packages/cli/dist/index.cjs +4541 -4005
- package/packages/cli/dist/index.cjs.map +1 -1
- package/packages/cli/dist/index.mjs +4 -4
- package/packages/cli/dist/index.mjs.map +1 -1
- package/packages/cli/dist/{src-J43ECFHC.mjs → src-M5J5YC5E.mjs} +3 -3
- package/packages/cli/dist/{src-J43ECFHC.mjs.map → src-M5J5YC5E.mjs.map} +1 -1
- package/packages/core/dist/advanced.cjs +3026 -2501
- package/packages/core/dist/advanced.cjs.map +1 -1
- package/packages/core/dist/advanced.d.cts +172 -16
- package/packages/core/dist/advanced.d.ts +172 -16
- package/packages/core/dist/advanced.mjs +362 -32
- package/packages/core/dist/advanced.mjs.map +1 -1
- package/packages/core/dist/checks.cjs +537 -152
- package/packages/core/dist/checks.cjs.map +1 -1
- package/packages/core/dist/checks.d.cts +9 -414
- package/packages/core/dist/checks.d.ts +9 -414
- package/packages/core/dist/checks.mjs +1 -1
- package/packages/core/dist/{chunk-LHSXRIYU.mjs → chunk-ME4WXYY3.mjs} +3609 -3224
- package/packages/core/dist/chunk-ME4WXYY3.mjs.map +1 -0
- package/packages/core/dist/{context-DOOT9GZ-.d.cts → context-DGMqxFpz.d.cts} +4 -4
- package/packages/core/dist/{context-DOX2-RjH.d.ts → context-DgcvkJ9t.d.ts} +4 -4
- package/packages/core/dist/diff.d.cts +3 -3
- package/packages/core/dist/diff.d.ts +3 -3
- package/packages/core/dist/exporters.d.cts +3 -3
- package/packages/core/dist/exporters.d.ts +3 -3
- package/packages/core/dist/{index-CtK_p-ps.d.ts → index-BOA1AeEU.d.ts} +473 -6
- package/packages/core/dist/{index-D72DPqAF.d.ts → index-BzyNPfLm.d.ts} +2 -2
- package/packages/core/dist/{index-DLLz9sce.d.cts → index-C6bCIbFO.d.cts} +2 -2
- package/packages/core/dist/{index-CR4CCRqP.d.cts → index-DVetZwGG.d.cts} +473 -6
- package/packages/core/dist/index.d.cts +7 -7
- package/packages/core/dist/index.d.ts +7 -7
- package/packages/core/dist/{inspect-event-DRthdZlf.d.cts → inspect-event-CDqjcrG_.d.cts} +1 -1
- package/packages/core/dist/{inspect-event-DRthdZlf.d.ts → inspect-event-CDqjcrG_.d.ts} +1 -1
- package/packages/core/dist/{log-config-CFlIJHTf.d.cts → log-config-DcjV8ZjF.d.cts} +1 -1
- package/packages/core/dist/{log-config-OZRfpHED.d.ts → log-config-N_c5AE09.d.ts} +1 -1
- package/packages/core/dist/logs.d.cts +3 -3
- package/packages/core/dist/logs.d.ts +3 -3
- package/packages/core/dist/{persisted-inspect-event-BVucFVrP.d.ts → persisted-inspect-event-CbaRYpbw.d.ts} +1 -1
- package/packages/core/dist/{persisted-inspect-event-CVZWZldL.d.cts → persisted-inspect-event-DiJ0I_vi.d.cts} +1 -1
- package/packages/core/dist/persisted.d.cts +5 -5
- package/packages/core/dist/persisted.d.ts +5 -5
- package/packages/core/dist/readers.d.cts +3 -3
- package/packages/core/dist/readers.d.ts +3 -3
- package/packages/core/dist/{types-B562HgN_.d.cts → types-BA_IOdg4.d.cts} +1 -1
- package/packages/core/dist/{types-B562HgN_.d.ts → types-BA_IOdg4.d.ts} +1 -1
- package/packages/core/dist/{types-VzKihKQo.d.cts → types-wXuxMUop.d.cts} +2 -2
- package/packages/core/dist/{types-Bx3mEZ04.d.ts → types-xv9bSgOK.d.ts} +2 -2
- package/packages/core/dist/writers.d.cts +2 -2
- package/packages/core/dist/writers.d.ts +2 -2
- package/packages/cli/dist/chunk-HPR2OJPU.mjs.map +0 -1
- 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/
|
|
1781
|
-
function
|
|
1782
|
-
return
|
|
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
|
|
1785
|
-
|
|
1786
|
-
|
|
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
|
-
|
|
1789
|
-
|
|
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
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
if (
|
|
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
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
return
|
|
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
|
|
1967
|
+
function argumentDigestOf(event) {
|
|
1828
1968
|
const attrs2 = event.attributes ?? {};
|
|
1829
|
-
for (const key of ["
|
|
1830
|
-
const
|
|
1831
|
-
if (
|
|
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
|
-
|
|
1836
|
-
|
|
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
|
|
1978
|
+
return void 0;
|
|
1839
1979
|
}
|
|
1840
|
-
function
|
|
1841
|
-
|
|
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
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
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
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
code: "
|
|
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 (
|
|
2010
|
+
if (a.value !== b.value) {
|
|
1857
2011
|
return {
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2012
|
+
ok: false,
|
|
2013
|
+
reason: "Retry arguments do not match prior attempt.",
|
|
2014
|
+
code: "AI_CHECK_RECOVERY_ARGUMENTS_DIFFER"
|
|
1861
2015
|
};
|
|
1862
2016
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
};
|
|
2070
|
+
return refs;
|
|
2071
|
+
}
|
|
2072
|
+
function llmReferencesTool(llm, toolEvent, requireExplicit) {
|
|
2073
|
+
if (!requireExplicit) {
|
|
2074
|
+
return eventTime2(llm) >= eventTime2(toolEvent);
|
|
1884
2075
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ? [
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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.`, [
|
|
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.`, [
|
|
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) =>
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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) =>
|
|
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
|
-
[
|
|
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: [
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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.`, [
|
|
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.`, [
|
|
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.`, [
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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) =>
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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 ? [
|
|
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) =>
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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) =>
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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
|
-
[
|
|
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] ? [
|
|
5102
|
+
evidence: guardrailEvents(context)[0] ? [eventEvidence3(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
|
|
4718
5103
|
}
|
|
4719
5104
|
];
|
|
4720
5105
|
for (const comparison of comparisons) {
|