agent-inspect 6.22.0 → 6.23.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.
@@ -1320,6 +1320,465 @@ function resolveTraceContractScope(input, scope) {
1320
1320
  };
1321
1321
  }
1322
1322
 
1323
+ // packages/core/src/checks/control-rules.ts
1324
+ function asStringList(value) {
1325
+ if (!Array.isArray(value)) return void 0;
1326
+ const out = [];
1327
+ for (const item of value) {
1328
+ if (typeof item !== "string" || item.trim() === "") return void 0;
1329
+ out.push(item.trim());
1330
+ }
1331
+ return out;
1332
+ }
1333
+ function attributeList(events, attribute) {
1334
+ if (!attribute) return void 0;
1335
+ for (const event of events) {
1336
+ const attrs2 = event.attributes;
1337
+ if (!attrs2 || typeof attrs2 !== "object") continue;
1338
+ const direct = asStringList(attrs2[attribute]);
1339
+ if (direct) return direct;
1340
+ const nested = attrs2.metadata && typeof attrs2.metadata === "object" ? asStringList(attrs2.metadata[attribute]) : void 0;
1341
+ if (nested) return nested;
1342
+ }
1343
+ return void 0;
1344
+ }
1345
+ function fail(ruleId, message, evidence, expected, actual) {
1346
+ return {
1347
+ ruleId,
1348
+ severity: "error",
1349
+ status: "fail",
1350
+ message,
1351
+ ...expected !== void 0 ? { expected } : {},
1352
+ ...actual !== void 0 ? { actual } : {},
1353
+ evidence: [...evidence]
1354
+ };
1355
+ }
1356
+ function sortedUnique(values) {
1357
+ return [...new Set(values)].sort((a, b) => a.localeCompare(b));
1358
+ }
1359
+ function setEqual(left, right) {
1360
+ const a = sortedUnique(left);
1361
+ const b = sortedUnique(right);
1362
+ return a.length === b.length && a.every((value, index) => value === b[index]);
1363
+ }
1364
+ function evaluateControlRules(events, rules, runEvidence2, observationNames) {
1365
+ const findings = [];
1366
+ const declared = rules.declaredTools ?? attributeList(events, rules.declaredToolsAttribute);
1367
+ const enforced = rules.enforcedTools ?? attributeList(events, rules.enforcedToolsAttribute);
1368
+ if (rules.requireDeclaredMatchesEnforced) {
1369
+ if (declared === void 0 || enforced === void 0) {
1370
+ findings.push(
1371
+ fail(
1372
+ "contract.controls.declared-matches-enforced",
1373
+ "Declared and enforced tool sets could not both be resolved.",
1374
+ runEvidence2,
1375
+ { declaredDefined: declared !== void 0, enforcedDefined: enforced !== void 0 },
1376
+ { code: "AI_CHECK_CONTROL_EVIDENCE_UNAVAILABLE" }
1377
+ )
1378
+ );
1379
+ } else if (!setEqual(declared, enforced)) {
1380
+ findings.push(
1381
+ fail(
1382
+ "contract.controls.declared-matches-enforced",
1383
+ "Declared tools differ from enforced tools.",
1384
+ runEvidence2,
1385
+ sortedUnique(declared),
1386
+ sortedUnique(enforced)
1387
+ )
1388
+ );
1389
+ }
1390
+ }
1391
+ const observedTools = sortedUnique(
1392
+ events.filter((event) => event.kind === "TOOL" && event.status !== "running").map((event) => resolveCanonicalToolName(event))
1393
+ );
1394
+ if (rules.requireObservedWithinEnforced) {
1395
+ if (enforced === void 0) {
1396
+ findings.push(
1397
+ fail(
1398
+ "contract.controls.observed-within-enforced",
1399
+ "Enforced tool set unavailable for observed-within-enforced check.",
1400
+ runEvidence2,
1401
+ void 0,
1402
+ { code: "AI_CHECK_CONTROL_EVIDENCE_UNAVAILABLE" }
1403
+ )
1404
+ );
1405
+ } else {
1406
+ const enforcedSet = new Set(enforced);
1407
+ const outside = observedTools.filter((name) => !enforcedSet.has(name));
1408
+ if (outside.length > 0) {
1409
+ findings.push(
1410
+ fail(
1411
+ "contract.controls.observed-within-enforced",
1412
+ `Observed tools outside enforced allowlist: ${outside.join(", ")}.`,
1413
+ runEvidence2,
1414
+ sortedUnique(enforced),
1415
+ outside
1416
+ )
1417
+ );
1418
+ }
1419
+ }
1420
+ }
1421
+ if (rules.requireObservedWithinDeclared) {
1422
+ if (declared === void 0) {
1423
+ findings.push(
1424
+ fail(
1425
+ "contract.controls.observed-within-declared",
1426
+ "Declared tool set unavailable for observed-within-declared check.",
1427
+ runEvidence2,
1428
+ void 0,
1429
+ { code: "AI_CHECK_CONTROL_EVIDENCE_UNAVAILABLE" }
1430
+ )
1431
+ );
1432
+ } else {
1433
+ const declaredSet = new Set(declared);
1434
+ const outside = observedTools.filter((name) => !declaredSet.has(name));
1435
+ if (outside.length > 0) {
1436
+ findings.push(
1437
+ fail(
1438
+ "contract.controls.observed-within-declared",
1439
+ `Observed tools outside declared set: ${outside.join(", ")}.`,
1440
+ runEvidence2,
1441
+ sortedUnique(declared),
1442
+ outside
1443
+ )
1444
+ );
1445
+ }
1446
+ }
1447
+ }
1448
+ for (const stage of rules.requiredStages ?? []) {
1449
+ const observation = stage.observation ?? `control.${stage.stage}`;
1450
+ if (!observationNames.has(observation)) {
1451
+ findings.push(
1452
+ fail(
1453
+ `contract.controls.stage.${stage.stage}`,
1454
+ `Required control stage observation missing: ${observation}.`,
1455
+ runEvidence2,
1456
+ observation,
1457
+ [...observationNames]
1458
+ )
1459
+ );
1460
+ }
1461
+ }
1462
+ return findings;
1463
+ }
1464
+
1465
+ // packages/core/src/checks/retry-safety.ts
1466
+ function fail2(ruleId, message, evidence, expected, actual) {
1467
+ return {
1468
+ ruleId,
1469
+ severity: "error",
1470
+ status: "fail",
1471
+ message,
1472
+ ...expected !== void 0 ? { expected } : {},
1473
+ ...actual !== void 0 ? { actual } : {},
1474
+ evidence: [...evidence]
1475
+ };
1476
+ }
1477
+ function workflowFor(event) {
1478
+ const attrs2 = event.attributes;
1479
+ if (!attrs2 || typeof attrs2 !== "object") return {};
1480
+ const direct = extractSessionWorkflowMetadata(attrs2);
1481
+ const nested = attrs2.metadata && typeof attrs2.metadata === "object" ? extractSessionWorkflowMetadata(attrs2.metadata) : void 0;
1482
+ return { ...nested, ...direct };
1483
+ }
1484
+ function hasIdempotencyEvidence(event) {
1485
+ const workflow = workflowFor(event);
1486
+ if (typeof workflow.idempotencyKey === "string" && workflow.idempotencyKey.trim() !== "") {
1487
+ return true;
1488
+ }
1489
+ const attrs2 = event.attributes ?? {};
1490
+ if (attrs2.noSideEffect === true) return true;
1491
+ if (attrs2.sideEffect === false) return true;
1492
+ return false;
1493
+ }
1494
+ function eventEvidence(event) {
1495
+ return {
1496
+ runId: event.runId,
1497
+ eventId: event.eventId,
1498
+ kind: event.kind,
1499
+ name: event.name,
1500
+ status: event.status
1501
+ };
1502
+ }
1503
+ function evaluateRetrySafetyRules(events, rules, _runEvidence) {
1504
+ const findings = [];
1505
+ const byOperation = /* @__PURE__ */ new Map();
1506
+ for (const event of events) {
1507
+ const workflow = workflowFor(event);
1508
+ const operationId = workflow.operationId;
1509
+ if (!operationId) continue;
1510
+ const list = byOperation.get(operationId) ?? [];
1511
+ list.push(event);
1512
+ byOperation.set(operationId, list);
1513
+ }
1514
+ if (rules.maxAttempts !== void 0) {
1515
+ for (const [operationId, members] of byOperation) {
1516
+ const attemptIds = new Set(
1517
+ members.map((event) => workflowFor(event).attemptId).filter((value) => typeof value === "string" && value.trim() !== "")
1518
+ );
1519
+ const attemptNumbers = members.map((event) => workflowFor(event).attemptNumber ?? workflowFor(event).attempt).filter((value) => typeof value === "number" && Number.isFinite(value));
1520
+ const count = attemptIds.size > 0 ? attemptIds.size : attemptNumbers.length > 0 ? Math.max(...attemptNumbers) : members.filter((event) => event.kind === "TOOL" || event.kind === "LLM").length;
1521
+ if (count > rules.maxAttempts) {
1522
+ findings.push(
1523
+ fail2(
1524
+ "contract.retry.max-attempts",
1525
+ `Operation ${operationId} exceeded maxAttempts ${rules.maxAttempts}.`,
1526
+ members.slice(0, 4).map(eventEvidence),
1527
+ rules.maxAttempts,
1528
+ count
1529
+ )
1530
+ );
1531
+ }
1532
+ }
1533
+ }
1534
+ if (rules.requireTerminalResult) {
1535
+ for (const [operationId, members] of byOperation) {
1536
+ const hasTerminal = members.some((event) => event.status === "ok" || event.status === "error");
1537
+ if (!hasTerminal) {
1538
+ findings.push(
1539
+ fail2(
1540
+ "contract.retry.terminal-result",
1541
+ `Operation ${operationId} has no terminal ok/error result.`,
1542
+ members.slice(0, 4).map(eventEvidence),
1543
+ "ok|error",
1544
+ members.map((event) => event.status)
1545
+ )
1546
+ );
1547
+ }
1548
+ }
1549
+ }
1550
+ if (rules.fallbackOnlyAfterFailure) {
1551
+ for (const event of events) {
1552
+ const workflow = workflowFor(event);
1553
+ const fallbackOf = workflow.fallbackOf;
1554
+ if (!fallbackOf) continue;
1555
+ const prior = byOperation.get(fallbackOf) ?? events.filter((candidate) => {
1556
+ const meta = workflowFor(candidate);
1557
+ return meta.operationId === fallbackOf || candidate.runId === fallbackOf;
1558
+ });
1559
+ const priorFailure = prior.some((candidate) => candidate.status === "error");
1560
+ if (!priorFailure) {
1561
+ findings.push(
1562
+ fail2(
1563
+ "contract.retry.fallback-after-failure",
1564
+ `Fallback for ${fallbackOf} appeared without a prior failure.`,
1565
+ [eventEvidence(event)],
1566
+ "prior error attempt",
1567
+ { fallbackOf }
1568
+ )
1569
+ );
1570
+ }
1571
+ }
1572
+ }
1573
+ const nonIdempotent = new Set(rules.nonIdempotentTools ?? []);
1574
+ if (nonIdempotent.size > 0 || rules.requireIdempotencyEvidenceForRetry) {
1575
+ const toolEvents = events.filter(
1576
+ (event) => event.kind === "TOOL" && event.status !== "running"
1577
+ );
1578
+ const byToolOp = /* @__PURE__ */ new Map();
1579
+ for (const event of toolEvents) {
1580
+ const name = resolveCanonicalToolName(event);
1581
+ const workflow = workflowFor(event);
1582
+ const key = `${workflow.operationId ?? name}::${name}`;
1583
+ const list = byToolOp.get(key) ?? [];
1584
+ list.push(event);
1585
+ byToolOp.set(key, list);
1586
+ }
1587
+ for (const [, members] of byToolOp) {
1588
+ const ordered = [...members].sort((a, b) => {
1589
+ const aTime = a.startedAt ?? a.timestamp ?? "";
1590
+ const bTime = b.startedAt ?? b.timestamp ?? "";
1591
+ return aTime.localeCompare(bTime);
1592
+ });
1593
+ let sawOk = false;
1594
+ let sawSideEffectOk = false;
1595
+ for (const event of ordered) {
1596
+ const name = resolveCanonicalToolName(event);
1597
+ const isRetry = sawOk;
1598
+ if (isRetry) {
1599
+ if (rules.requireIdempotencyEvidenceForRetry && !hasIdempotencyEvidence(event)) {
1600
+ findings.push(
1601
+ fail2(
1602
+ "contract.retry.idempotency-evidence",
1603
+ `Retry of tool ${name} lacks idempotencyKey / noSideEffect evidence.`,
1604
+ [eventEvidence(event)],
1605
+ "idempotencyKey|noSideEffect",
1606
+ { code: "AI_CHECK_RETRY_EVIDENCE_UNAVAILABLE" }
1607
+ )
1608
+ );
1609
+ }
1610
+ if (nonIdempotent.has(name) && sawSideEffectOk && !hasIdempotencyEvidence(event)) {
1611
+ findings.push(
1612
+ fail2(
1613
+ "contract.retry.non-idempotent-side-effect",
1614
+ `Retry of non-idempotent tool ${name} after a confirmed ok side effect.`,
1615
+ [eventEvidence(event)],
1616
+ "no retry after side effect",
1617
+ name
1618
+ )
1619
+ );
1620
+ }
1621
+ }
1622
+ if (event.status === "ok") {
1623
+ sawOk = true;
1624
+ if (nonIdempotent.has(name) && !hasIdempotencyEvidence(event)) {
1625
+ sawSideEffectOk = true;
1626
+ }
1627
+ }
1628
+ }
1629
+ }
1630
+ }
1631
+ if (rules.requireRecoveredFailureVisible) {
1632
+ for (const [operationId, members] of byOperation) {
1633
+ const hasOk = members.some((event) => event.status === "ok");
1634
+ const hasError = members.some((event) => event.status === "error");
1635
+ const attemptish = members.some((event) => {
1636
+ const meta = workflowFor(event);
1637
+ return meta.attemptId !== void 0 || meta.attemptNumber !== void 0 || meta.attempt !== void 0;
1638
+ }) || members.length > 1;
1639
+ if (hasOk && attemptish && !hasError) {
1640
+ findings.push(
1641
+ fail2(
1642
+ "contract.retry.recovered-failure-visible",
1643
+ `Operation ${operationId} recovered without retaining a visible failure attempt.`,
1644
+ members.slice(0, 4).map(eventEvidence),
1645
+ "error attempt retained",
1646
+ { hasOk, hasError }
1647
+ )
1648
+ );
1649
+ }
1650
+ }
1651
+ }
1652
+ return findings;
1653
+ }
1654
+
1655
+ // packages/core/src/checks/tool-arguments.ts
1656
+ function decodeToken(token) {
1657
+ return token.replace(/~1/g, "/").replace(/~0/g, "~");
1658
+ }
1659
+ function resolveJsonPointer(document, pointer) {
1660
+ if (pointer === "") {
1661
+ return { found: true, value: document };
1662
+ }
1663
+ if (!pointer.startsWith("/")) {
1664
+ return { found: false };
1665
+ }
1666
+ const tokens = pointer.slice(1).split("/").map(decodeToken);
1667
+ let current = document;
1668
+ for (const token of tokens) {
1669
+ if (current === null || current === void 0) {
1670
+ return { found: false };
1671
+ }
1672
+ if (Array.isArray(current)) {
1673
+ if (!/^(0|[1-9][0-9]*)$/.test(token)) {
1674
+ return { found: false };
1675
+ }
1676
+ const index = Number(token);
1677
+ if (index >= current.length) {
1678
+ return { found: false };
1679
+ }
1680
+ current = current[index];
1681
+ continue;
1682
+ }
1683
+ if (typeof current !== "object") {
1684
+ return { found: false };
1685
+ }
1686
+ const record = current;
1687
+ if (!Object.prototype.hasOwnProperty.call(record, token)) {
1688
+ return { found: false };
1689
+ }
1690
+ current = record[token];
1691
+ }
1692
+ return { found: true, value: current };
1693
+ }
1694
+ function jsonType(value) {
1695
+ if (value === null) return "null";
1696
+ if (Array.isArray(value)) return "array";
1697
+ return typeof value;
1698
+ }
1699
+ function valuesEqual(left, right) {
1700
+ return JSON.stringify(left) === JSON.stringify(right);
1701
+ }
1702
+ function extractToolArgumentPayload(event) {
1703
+ const attrs2 = event.attributes ?? {};
1704
+ for (const key of ["arguments", "input", "toolArguments"]) {
1705
+ const candidate = attrs2[key];
1706
+ if (candidate !== void 0 && candidate !== null && typeof candidate === "object") {
1707
+ return { present: true, value: candidate };
1708
+ }
1709
+ }
1710
+ if (event.inputSummary !== void 0 && event.inputSummary !== null && typeof event.inputSummary === "object") {
1711
+ return { present: true, value: event.inputSummary };
1712
+ }
1713
+ return { present: false, value: void 0 };
1714
+ }
1715
+ function evaluateToolArgumentValue(value, check, evidencePresent) {
1716
+ if (!evidencePresent) {
1717
+ return {
1718
+ status: "unavailable",
1719
+ code: "AI_CHECK_TOOL_ARGUMENT_EVIDENCE_UNAVAILABLE",
1720
+ message: `Structured argument evidence unavailable for tool ${check.tool} at ${check.path}.`
1721
+ };
1722
+ }
1723
+ const resolved = resolveJsonPointer(value, check.path);
1724
+ if (check.operator === "exists") {
1725
+ return resolved.found ? { status: "pass" } : {
1726
+ status: "fail",
1727
+ code: "AI_CHECK_TOOL_ARGUMENT_MISSING",
1728
+ message: `Expected path ${check.path} to exist on tool ${check.tool}.`
1729
+ };
1730
+ }
1731
+ if (!resolved.found) {
1732
+ return {
1733
+ status: "unavailable",
1734
+ code: "AI_CHECK_TOOL_ARGUMENT_EVIDENCE_UNAVAILABLE",
1735
+ message: `Path ${check.path} missing for tool ${check.tool}.`
1736
+ };
1737
+ }
1738
+ if (check.operator === "type") {
1739
+ const actual = jsonType(resolved.value);
1740
+ if (check.type === void 0) {
1741
+ return {
1742
+ status: "fail",
1743
+ code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1744
+ message: "type operator requires check.type"
1745
+ };
1746
+ }
1747
+ return actual === check.type ? { status: "pass" } : {
1748
+ status: "fail",
1749
+ code: "AI_CHECK_TOOL_ARGUMENT_TYPE",
1750
+ message: `Expected type ${check.type} at ${check.path} for tool ${check.tool}; found ${actual}.`
1751
+ };
1752
+ }
1753
+ if (check.operator === "equals") {
1754
+ return valuesEqual(resolved.value, check.expected) ? { status: "pass" } : {
1755
+ status: "fail",
1756
+ code: "AI_CHECK_TOOL_ARGUMENT_EQUALS",
1757
+ message: `Value at ${check.path} for tool ${check.tool} did not equal expected (bounded comparison).`
1758
+ };
1759
+ }
1760
+ if (check.operator === "oneOf") {
1761
+ const options = check.oneOf ?? [];
1762
+ if (options.length === 0) {
1763
+ return {
1764
+ status: "fail",
1765
+ code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1766
+ message: "oneOf operator requires a non-empty oneOf array"
1767
+ };
1768
+ }
1769
+ return options.some((option) => valuesEqual(resolved.value, option)) ? { status: "pass" } : {
1770
+ status: "fail",
1771
+ code: "AI_CHECK_TOOL_ARGUMENT_ONE_OF",
1772
+ message: `Value at ${check.path} for tool ${check.tool} was not in the allowed oneOf set.`
1773
+ };
1774
+ }
1775
+ return {
1776
+ status: "fail",
1777
+ code: "AI_CHECK_TOOL_ARGUMENT_INVALID_CONFIG",
1778
+ message: `Unsupported operator.`
1779
+ };
1780
+ }
1781
+
1323
1782
  // packages/core/src/checks/contract.ts
1324
1783
  function contractFailFinding(ruleId, message, evidence, expected, actual) {
1325
1784
  return {
@@ -1341,18 +1800,38 @@ function normalizeStatus(status) {
1341
1800
  function cloneBody(body) {
1342
1801
  return {
1343
1802
  ...body.run ? { run: { ...body.run } } : {},
1344
- ...body.tools ? { tools: { ...body.tools } } : {},
1803
+ ...body.tools ? {
1804
+ tools: {
1805
+ ...body.tools,
1806
+ ...body.tools.arguments ? { arguments: body.tools.arguments.map((item) => ({ ...item })) } : {},
1807
+ ...body.tools.orderRules ? { orderRules: body.tools.orderRules.map((item) => ({ ...item })) } : {}
1808
+ }
1809
+ } : {},
1345
1810
  ...body.llm ? { llm: { ...body.llm } } : {},
1346
1811
  ...body.observations ? {
1347
1812
  observations: {
1348
1813
  ...body.observations,
1349
1814
  ...body.observations.requireProvenance ? { requireProvenance: { ...body.observations.requireProvenance } } : {}
1350
1815
  }
1816
+ } : {},
1817
+ ...body.controls ? {
1818
+ controls: {
1819
+ ...body.controls,
1820
+ ...body.controls.declaredTools ? { declaredTools: [...body.controls.declaredTools] } : {},
1821
+ ...body.controls.enforcedTools ? { enforcedTools: [...body.controls.enforcedTools] } : {},
1822
+ ...body.controls.requiredStages ? { requiredStages: body.controls.requiredStages.map((item) => ({ ...item })) } : {}
1823
+ }
1824
+ } : {},
1825
+ ...body.retry ? {
1826
+ retry: {
1827
+ ...body.retry,
1828
+ ...body.retry.nonIdempotentTools ? { nonIdempotentTools: [...body.retry.nonIdempotentTools] } : {}
1829
+ }
1351
1830
  } : {}
1352
1831
  };
1353
1832
  }
1354
1833
  function bodyHasRules(body) {
1355
- return body.run !== void 0 || body.tools !== void 0 || body.llm !== void 0 || body.observations !== void 0;
1834
+ return body.run !== void 0 || body.tools !== void 0 || body.llm !== void 0 || body.observations !== void 0 || body.controls !== void 0 || body.retry !== void 0;
1356
1835
  }
1357
1836
  var MAX_EVIDENCE_EVENT_IDS = 16;
1358
1837
  var METHOD_VOCABULARY = new Set(OBSERVED_OUTCOME_METHODS);
@@ -1553,11 +2032,14 @@ function contractToRules(contract) {
1553
2032
  if (contract.tools) {
1554
2033
  const order = contract.tools.requiredOrder ?? [];
1555
2034
  const requiredOrderMode = contract.tools.requiredOrderMode ?? "first-occurrence";
2035
+ const orderRules = contract.tools.orderRules ?? [];
2036
+ const endpointRequired = orderRules.filter((rule) => rule.requireEndpoints !== false).flatMap((rule) => [rule.before, rule.after]);
1556
2037
  const required = [
1557
2038
  .../* @__PURE__ */ new Set([
1558
2039
  ...contract.tools.required ?? [],
1559
2040
  ...contract.tools.requiredTools ?? [],
1560
- ...order
2041
+ ...order,
2042
+ ...endpointRequired
1561
2043
  ])
1562
2044
  ];
1563
2045
  const forbidden = [
@@ -1582,6 +2064,106 @@ function contractToRules(contract) {
1582
2064
  })
1583
2065
  );
1584
2066
  }
2067
+ const defaultOccurrenceMode = contract.tools.defaultOccurrenceMode ?? requiredOrderMode;
2068
+ for (const [index, rule] of orderRules.entries()) {
2069
+ rules.push(
2070
+ createToolOrderingRule({
2071
+ before: rule.before,
2072
+ after: rule.after,
2073
+ id: `contract.tool.orderRule.${index}`,
2074
+ mode: rule.occurrenceMode ?? defaultOccurrenceMode
2075
+ })
2076
+ );
2077
+ }
2078
+ const argumentChecks = contract.tools.arguments ?? [];
2079
+ if (argumentChecks.length > 0) {
2080
+ rules.push({
2081
+ id: "contract.tool.arguments",
2082
+ category: "tool",
2083
+ defaultSeverity: "error",
2084
+ evaluate(context) {
2085
+ const tools = (context.logicalEvents ?? context.events).filter(
2086
+ (event) => event.kind === "TOOL" && event.status !== "running"
2087
+ );
2088
+ const findings = [];
2089
+ for (const [index, check] of argumentChecks.entries()) {
2090
+ const matches = tools.filter(
2091
+ (event) => resolveCanonicalToolName(event) === check.tool
2092
+ );
2093
+ if (matches.length === 0) {
2094
+ findings.push(
2095
+ contractFailFinding(
2096
+ `contract.tool.arguments.${index}`,
2097
+ `No finished tool named ${check.tool} for argument check.`,
2098
+ context.selectedRun ? [
2099
+ {
2100
+ runId: context.selectedRun.runId,
2101
+ kind: "RUN",
2102
+ name: context.selectedRun.name
2103
+ }
2104
+ ] : [],
2105
+ { tool: check.tool, path: check.path },
2106
+ { toolCount: 0 }
2107
+ )
2108
+ );
2109
+ continue;
2110
+ }
2111
+ const occurrence = check.occurrence ?? "all";
2112
+ const selected = occurrence === "first" ? [matches[0]] : occurrence === "last" ? [matches[matches.length - 1]] : matches;
2113
+ const results = selected.map((event) => {
2114
+ const payload = extractToolArgumentPayload(event);
2115
+ return evaluateToolArgumentValue(
2116
+ payload.value,
2117
+ check,
2118
+ payload.present
2119
+ );
2120
+ });
2121
+ if (occurrence === "any") {
2122
+ if (results.some((result) => result.status === "pass")) continue;
2123
+ const firstFail = results.find((result) => result.status !== "pass");
2124
+ findings.push(
2125
+ contractFailFinding(
2126
+ `contract.tool.arguments.${index}`,
2127
+ firstFail.message,
2128
+ selected.slice(0, 1).map((event) => ({
2129
+ runId: event.runId,
2130
+ eventId: event.eventId,
2131
+ kind: event.kind,
2132
+ name: event.name,
2133
+ path: `tool.${check.tool}${check.path}`
2134
+ })),
2135
+ { tool: check.tool, path: check.path, operator: check.operator },
2136
+ { code: firstFail.code }
2137
+ )
2138
+ );
2139
+ continue;
2140
+ }
2141
+ for (const [selIndex, result] of results.entries()) {
2142
+ if (result.status === "pass") continue;
2143
+ const event = selected[selIndex];
2144
+ findings.push(
2145
+ contractFailFinding(
2146
+ `contract.tool.arguments.${index}`,
2147
+ result.message,
2148
+ [
2149
+ {
2150
+ runId: event.runId,
2151
+ eventId: event.eventId,
2152
+ kind: event.kind,
2153
+ name: event.name,
2154
+ path: `tool.${check.tool}${check.path}`
2155
+ }
2156
+ ],
2157
+ { tool: check.tool, path: check.path, operator: check.operator },
2158
+ { code: result.code }
2159
+ )
2160
+ );
2161
+ }
2162
+ }
2163
+ return findings;
2164
+ }
2165
+ });
2166
+ }
1585
2167
  }
1586
2168
  if (contract.llm) {
1587
2169
  rules.push(
@@ -1642,6 +2224,46 @@ function contractToRules(contract) {
1642
2224
  });
1643
2225
  }
1644
2226
  }
2227
+ if (contract.controls) {
2228
+ const controls = contract.controls;
2229
+ rules.push({
2230
+ id: "contract.controls",
2231
+ category: "run",
2232
+ defaultSeverity: "error",
2233
+ evaluate(context) {
2234
+ const events = context.logicalEvents ?? context.events;
2235
+ const outcomes = extractOutcomesFromPersistedEvents(context.events);
2236
+ const observationNames = new Set(outcomes.map((item) => item.name));
2237
+ const runEvidence2 = context.selectedRun ? [
2238
+ {
2239
+ runId: context.selectedRun.runId,
2240
+ kind: "RUN",
2241
+ name: context.selectedRun.name
2242
+ }
2243
+ ] : [];
2244
+ return evaluateControlRules(events, controls, runEvidence2, observationNames);
2245
+ }
2246
+ });
2247
+ }
2248
+ if (contract.retry) {
2249
+ const retry = contract.retry;
2250
+ rules.push({
2251
+ id: "contract.retry",
2252
+ category: "run",
2253
+ defaultSeverity: "error",
2254
+ evaluate(context) {
2255
+ const events = context.logicalEvents ?? context.events;
2256
+ context.selectedRun ? [
2257
+ {
2258
+ runId: context.selectedRun.runId,
2259
+ kind: "RUN",
2260
+ name: context.selectedRun.name
2261
+ }
2262
+ ] : [];
2263
+ return evaluateRetrySafetyRules(events, retry);
2264
+ }
2265
+ });
2266
+ }
1645
2267
  return rules;
1646
2268
  }
1647
2269
  function validateScopeShape(scope) {
@@ -1728,7 +2350,7 @@ function validateAlternativesShape(alternatives) {
1728
2350
  diagnostics.push({
1729
2351
  code: "contract.alternatives.empty-branch",
1730
2352
  severity: "error",
1731
- message: `Branch ${branch.id} must declare at least one run/tools/llm/observations rule.`,
2353
+ message: `Branch ${branch.id} must declare at least one run/tools/llm/observations/controls/retry rule.`,
1732
2354
  path: `${path3}.contract`
1733
2355
  });
1734
2356
  }
@@ -2001,6 +2623,40 @@ function lintTraceContract(contract) {
2001
2623
  path: "tools.requiredOrderMode"
2002
2624
  });
2003
2625
  }
2626
+ const orderRules = contract.tools?.orderRules ?? [];
2627
+ const seenPairs = /* @__PURE__ */ new Set();
2628
+ for (const [index, rule] of orderRules.entries()) {
2629
+ const key = `${rule.before}\0${rule.after}`;
2630
+ if (seenPairs.has(key)) {
2631
+ diagnostics.push({
2632
+ code: "contract.tools.orderRules.duplicate",
2633
+ severity: "warning",
2634
+ message: `Duplicate orderRules pair ${rule.before} \u2192 ${rule.after}.`,
2635
+ path: `tools.orderRules[${index}]`
2636
+ });
2637
+ }
2638
+ seenPairs.add(key);
2639
+ if (rule.before === rule.after) {
2640
+ diagnostics.push({
2641
+ code: "contract.tools.orderRules.self",
2642
+ severity: "error",
2643
+ message: "orderRules before and after must differ.",
2644
+ path: `tools.orderRules[${index}]`
2645
+ });
2646
+ }
2647
+ }
2648
+ if ((contract.tools?.arguments?.length ?? 0) > 0) {
2649
+ for (const [index, check] of (contract.tools?.arguments ?? []).entries()) {
2650
+ if (!check.path.startsWith("/") && check.path !== "") {
2651
+ diagnostics.push({
2652
+ code: "contract.tools.arguments.path",
2653
+ severity: "error",
2654
+ message: 'tools.arguments path must be a JSON Pointer ("" or start with "/").',
2655
+ path: `tools.arguments[${index}].path`
2656
+ });
2657
+ }
2658
+ }
2659
+ }
2004
2660
  return diagnostics;
2005
2661
  }
2006
2662
  function explainTraceContract(contract) {
@@ -2044,6 +2700,17 @@ function explainTraceContract(contract) {
2044
2700
  `Base: requiredOrder [${contract.tools.requiredOrder.join(" \u2192 ")}] mode=${mode}.`
2045
2701
  );
2046
2702
  }
2703
+ if ((contract.tools.orderRules?.length ?? 0) > 0) {
2704
+ const defaultMode = contract.tools.defaultOccurrenceMode ?? "first-occurrence";
2705
+ lines.push(
2706
+ `Base: ${contract.tools.orderRules.length} orderRules (defaultOccurrenceMode=${defaultMode}).`
2707
+ );
2708
+ }
2709
+ if ((contract.tools.arguments?.length ?? 0) > 0) {
2710
+ lines.push(
2711
+ `Base: ${contract.tools.arguments.length} structured tool-argument check(s).`
2712
+ );
2713
+ }
2047
2714
  }
2048
2715
  if (contract.llm) {
2049
2716
  if (contract.llm.maxCalls !== void 0) {
@@ -2074,6 +2741,12 @@ function explainTraceContract(contract) {
2074
2741
  );
2075
2742
  }
2076
2743
  }
2744
+ if (contract.controls) {
2745
+ lines.push("Base: declared-versus-enforced control checks enabled.");
2746
+ }
2747
+ if (contract.retry) {
2748
+ lines.push("Base: retry/side-effect safety checks enabled.");
2749
+ }
2077
2750
  const branches = contract.alternatives?.anyOf ?? [];
2078
2751
  if (branches.length > 0) {
2079
2752
  lines.push(
@@ -2423,7 +3096,7 @@ function stripPrefix(name, prefixes) {
2423
3096
  }
2424
3097
  return name;
2425
3098
  }
2426
- function eventEvidence(event, path3) {
3099
+ function eventEvidence2(event, path3) {
2427
3100
  return {
2428
3101
  runId: event.runId,
2429
3102
  eventId: event.eventId,
@@ -2693,7 +3366,7 @@ function guardrailShape(context) {
2693
3366
  }
2694
3367
  function firstEvidenceForKind(context, kind, path3) {
2695
3368
  const event = semanticEvents(context).find((candidate) => candidate.kind === kind);
2696
- return event ? [eventEvidence(event, path3)] : runEvidence(context.selectedRun);
3369
+ return event ? [eventEvidence2(event, path3)] : runEvidence(context.selectedRun);
2697
3370
  }
2698
3371
  function baselineDiffFinding(message, evidence, expected, actual) {
2699
3372
  return failFinding("baseline.regression", message, evidence, expected, actual);
@@ -2726,7 +3399,7 @@ function createRunStatusRule(options = {}) {
2726
3399
  failFinding(
2727
3400
  "run.status",
2728
3401
  "Run contains incomplete running events.",
2729
- running.map((event) => eventEvidence(event)),
3402
+ running.map((event) => eventEvidence2(event)),
2730
3403
  "no running events",
2731
3404
  running.length
2732
3405
  )
@@ -2772,7 +3445,7 @@ function createMaxStepDurationRule(options) {
2772
3445
  failFinding(
2773
3446
  "run.maxStepDuration",
2774
3447
  `${over.length} step(s) exceeded max duration ${options.maxDurationMs}ms.`,
2775
- over.map((event) => eventEvidence(event, "durationMs")),
3448
+ over.map((event) => eventEvidence2(event, "durationMs")),
2776
3449
  { maxDurationMs: options.maxDurationMs },
2777
3450
  over.map((event) => ({
2778
3451
  eventId: event.eventId,
@@ -2798,7 +3471,7 @@ function createStallDetectionRule(options = {}) {
2798
3471
  failFinding(
2799
3472
  "run.stall",
2800
3473
  `Found ${running.length} event(s) still running (possible stall).`,
2801
- running.map((event) => eventEvidence(event, "status")),
3474
+ running.map((event) => eventEvidence2(event, "status")),
2802
3475
  "no running events",
2803
3476
  running.length
2804
3477
  )
@@ -2813,7 +3486,7 @@ function createStallDetectionRule(options = {}) {
2813
3486
  failFinding(
2814
3487
  "run.stall",
2815
3488
  `Found ${incomplete.length} started event(s) without endedAt.`,
2816
- incomplete.map((event) => eventEvidence(event, "endedAt")),
3489
+ incomplete.map((event) => eventEvidence2(event, "endedAt")),
2817
3490
  "endedAt for started events",
2818
3491
  incomplete.length
2819
3492
  )
@@ -2849,7 +3522,7 @@ function createRequireCompletedRule() {
2849
3522
  failFinding(
2850
3523
  "run.requireCompleted",
2851
3524
  `Run has ${running.length} incomplete running event(s).`,
2852
- running.map((event) => eventEvidence(event, "status")),
3525
+ running.map((event) => eventEvidence2(event, "status")),
2853
3526
  "no running events",
2854
3527
  running.length
2855
3528
  )
@@ -2947,12 +3620,12 @@ function createToolUsageRule(options) {
2947
3620
  const name = toolName(event);
2948
3621
  if (forbidden.has(name)) {
2949
3622
  findings.push(
2950
- failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence(event)], "tool absent", name)
3623
+ failFinding("tool.usage", `Forbidden tool ${name} appeared.`, [eventEvidence2(event)], "tool absent", name)
2951
3624
  );
2952
3625
  }
2953
3626
  if (allowed && !allowed.has(name)) {
2954
3627
  findings.push(
2955
- failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence(event)], [...allowed].sort(), name)
3628
+ failFinding("tool.usage", `Tool ${name} is not in the allowed tool set.`, [eventEvidence2(event)], [...allowed].sort(), name)
2956
3629
  );
2957
3630
  }
2958
3631
  }
@@ -2963,7 +3636,7 @@ function createToolUsageRule(options) {
2963
3636
  }
2964
3637
  if (options.maxCount !== void 0 && tools.length > options.maxCount) {
2965
3638
  findings.push(
2966
- failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence(event)), { maxCount: options.maxCount }, tools.length)
3639
+ failFinding("tool.usage", `Tool count ${tools.length} exceeded maximum ${options.maxCount}.`, tools.map((event) => eventEvidence2(event)), { maxCount: options.maxCount }, tools.length)
2967
3640
  );
2968
3641
  }
2969
3642
  return findings;
@@ -2990,7 +3663,7 @@ function createToolOrderingRule(options) {
2990
3663
  failFinding(
2991
3664
  ruleId,
2992
3665
  `Tool ${options.before} must appear before ${options.after}.`,
2993
- [eventEvidence(tools[beforeIndex]), eventEvidence(tools[afterIndex])],
3666
+ [eventEvidence2(tools[beforeIndex]), eventEvidence2(tools[afterIndex])],
2994
3667
  { before: options.before, after: options.after },
2995
3668
  tools.map(toolName)
2996
3669
  )
@@ -3012,7 +3685,7 @@ function createToolOrderingRule(options) {
3012
3685
  failFinding(
3013
3686
  ruleId,
3014
3687
  `Tool ${options.before} cannot causally happen before itself.`,
3015
- [eventEvidence(beforeEvent)],
3688
+ [eventEvidence2(beforeEvent)],
3016
3689
  expected,
3017
3690
  { code: "tool.order.same-tool" }
3018
3691
  )
@@ -3023,7 +3696,7 @@ function createToolOrderingRule(options) {
3023
3696
  failFinding(
3024
3697
  ruleId,
3025
3698
  `Tool order ${options.before} before ${options.after} could not establish causal timing.`,
3026
- [eventEvidence(beforeEvent), eventEvidence(afterEvent)],
3699
+ [eventEvidence2(beforeEvent), eventEvidence2(afterEvent)],
3027
3700
  expected,
3028
3701
  {
3029
3702
  code: "tool.order.interval-unresolved",
@@ -3038,7 +3711,7 @@ function createToolOrderingRule(options) {
3038
3711
  failFinding(
3039
3712
  ruleId,
3040
3713
  `Tool ${options.before} must finish before ${options.after} starts.`,
3041
- [eventEvidence(beforeEvent), eventEvidence(afterEvent)],
3714
+ [eventEvidence2(beforeEvent), eventEvidence2(afterEvent)],
3042
3715
  expected,
3043
3716
  {
3044
3717
  beforeEndedAt: new Date(beforeEnd).toISOString(),
@@ -3063,7 +3736,7 @@ function createToolOrderingRule(options) {
3063
3736
  failFinding(
3064
3737
  ruleId,
3065
3738
  `Tool ${options.before} cannot causally happen before itself.`,
3066
- [eventEvidence(beforeEvent)],
3739
+ [eventEvidence2(beforeEvent)],
3067
3740
  expected,
3068
3741
  { code: "tool.order.same-tool" }
3069
3742
  )
@@ -3098,7 +3771,7 @@ function createToolOrderingRule(options) {
3098
3771
  failFinding(
3099
3772
  ruleId,
3100
3773
  `Tool order ${options.before} before ${options.after} could not establish all causal intervals.`,
3101
- [firstMissingBefore, firstMissingAfter].filter((event) => event !== void 0).map((event) => eventEvidence(event)),
3774
+ [firstMissingBefore, firstMissingAfter].filter((event) => event !== void 0).map((event) => eventEvidence2(event)),
3102
3775
  expected,
3103
3776
  {
3104
3777
  code: "tool.order.interval-unresolved",
@@ -3113,7 +3786,7 @@ function createToolOrderingRule(options) {
3113
3786
  failFinding(
3114
3787
  ruleId,
3115
3788
  `Every ${options.before} tool call must finish before any ${options.after} tool call starts.`,
3116
- [eventEvidence(latestBefore.event), eventEvidence(earliestAfter.event)],
3789
+ [eventEvidence2(latestBefore.event), eventEvidence2(earliestAfter.event)],
3117
3790
  expected,
3118
3791
  {
3119
3792
  latestBeforeEndedAt: new Date(latestBefore.end).toISOString(),
@@ -3137,7 +3810,7 @@ function createToolOrderingRule(options) {
3137
3810
  beforeEndedAt: beforeEvent.endedAt,
3138
3811
  afterStartedAt: afterEvent.startedAt ?? afterEvent.timestamp
3139
3812
  },
3140
- evidence: [eventEvidence(beforeEvent), eventEvidence(afterEvent)]
3813
+ evidence: [eventEvidence2(beforeEvent), eventEvidence2(afterEvent)]
3141
3814
  }
3142
3815
  ];
3143
3816
  }
@@ -3160,7 +3833,7 @@ function createToolFailureRule(options) {
3160
3833
  failFinding(
3161
3834
  "tool.failures",
3162
3835
  `Tool failure count ${failures.length} exceeded ${options.maxFailures}.`,
3163
- failures.map((event) => eventEvidence(event)),
3836
+ failures.map((event) => eventEvidence2(event)),
3164
3837
  { maxFailures: options.maxFailures },
3165
3838
  failures.length
3166
3839
  )
@@ -3173,7 +3846,7 @@ function createToolFailureRule(options) {
3173
3846
  failFinding(
3174
3847
  "tool.failures",
3175
3848
  `Tool retry count exceeded ${options.maxRetries}.`,
3176
- excessiveRetries.map((item) => eventEvidence(item.event, "attributes.retryCount")),
3849
+ excessiveRetries.map((item) => eventEvidence2(item.event, "attributes.retryCount")),
3177
3850
  { maxRetries: options.maxRetries },
3178
3851
  excessiveRetries.map((item) => ({ tool: toolName(item.event), retries: item.count }))
3179
3852
  )
@@ -3200,7 +3873,7 @@ function createLlmUsageRule(options) {
3200
3873
  failFinding(
3201
3874
  "llm.usage",
3202
3875
  `LLM call count ${llms.length} exceeded ${options.maxCalls}.`,
3203
- llms.map((event) => eventEvidence(event)),
3876
+ llms.map((event) => eventEvidence2(event)),
3204
3877
  { maxCalls: options.maxCalls },
3205
3878
  llms.length
3206
3879
  )
@@ -3212,17 +3885,17 @@ function createLlmUsageRule(options) {
3212
3885
  const finishReason = llmFinishReason(event);
3213
3886
  if (allowedModels && (!model || !allowedModels.has(model))) {
3214
3887
  findings.push(
3215
- failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
3888
+ failFinding("llm.usage", `LLM model ${model ?? "unknown"} is not allowed.`, [eventEvidence2(event, "attributes.model")], [...allowedModels].sort(), model ?? "unknown")
3216
3889
  );
3217
3890
  }
3218
3891
  if (allowedProviders && (!provider || !allowedProviders.has(provider))) {
3219
3892
  findings.push(
3220
- failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
3893
+ failFinding("llm.usage", `LLM provider ${provider ?? "unknown"} is not allowed.`, [eventEvidence2(event, "attributes.provider")], [...allowedProviders].sort(), provider ?? "unknown")
3221
3894
  );
3222
3895
  }
3223
3896
  if (finishReasons && (!finishReason || !finishReasons.has(finishReason))) {
3224
3897
  findings.push(
3225
- failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
3898
+ failFinding("llm.usage", `LLM finish reason ${finishReason ?? "unknown"} is not allowed.`, [eventEvidence2(event, "attributes.finishReason")], [...finishReasons].sort(), finishReason ?? "unknown")
3226
3899
  );
3227
3900
  }
3228
3901
  }
@@ -3247,7 +3920,7 @@ function createLlmUsageRule(options) {
3247
3920
  failFinding(
3248
3921
  "llm.usage",
3249
3922
  `LLM ${key} token count ${tokenTotals[key]} exceeded ${limit}.`,
3250
- llms.map((event) => eventEvidence(event, `tokenUsage.${key}`)),
3923
+ llms.map((event) => eventEvidence2(event, `tokenUsage.${key}`)),
3251
3924
  { [`max${key[0].toUpperCase()}${key.slice(1)}Tokens`]: limit },
3252
3925
  tokenTotals[key]
3253
3926
  )
@@ -3272,7 +3945,7 @@ function createStructureIncompleteRule(options = {}) {
3272
3945
  failFinding(
3273
3946
  "structure.incomplete",
3274
3947
  "Trace contains incomplete running events.",
3275
- running.map((event) => eventEvidence(event, "status")),
3948
+ running.map((event) => eventEvidence2(event, "status")),
3276
3949
  "no running events",
3277
3950
  running.length
3278
3951
  )
@@ -3288,7 +3961,7 @@ function createStructureIncompleteRule(options = {}) {
3288
3961
  failFinding(
3289
3962
  "structure.incomplete",
3290
3963
  "Trace contains events with startedAt but no endedAt.",
3291
- missingEndedAt.map((event) => eventEvidence(event, "endedAt")),
3964
+ missingEndedAt.map((event) => eventEvidence2(event, "endedAt")),
3292
3965
  "endedAt for started events",
3293
3966
  missingEndedAt.length
3294
3967
  )
@@ -3316,7 +3989,7 @@ function createStructureOrphanRule(options = {}) {
3316
3989
  failFinding(
3317
3990
  "structure.orphan",
3318
3991
  "Trace contains events whose parentId is not present in the selected run.",
3319
- orphans.map((event) => eventEvidence(event, "parentId")),
3992
+ orphans.map((event) => eventEvidence2(event, "parentId")),
3320
3993
  "parentId resolves to an event in the selected run",
3321
3994
  orphans.length
3322
3995
  )
@@ -3348,7 +4021,7 @@ function createStructureCycleRule() {
3348
4021
  failFinding(
3349
4022
  "structure.cycle",
3350
4023
  formatProgrammaticDiagnostic("AI_TRACE_RELATIONSHIP_CYCLE"),
3351
- cycle.map((item) => eventEvidence(item, "parentId")),
4024
+ cycle.map((item) => eventEvidence2(item, "parentId")),
3352
4025
  "acyclic parentId graph",
3353
4026
  cycle.map((item) => item.eventId).sort()
3354
4027
  )
@@ -3380,7 +4053,7 @@ function createStructureRelationshipRule(options = {}) {
3380
4053
  failFinding(
3381
4054
  "structure.relationship",
3382
4055
  `Event confidence ${event.confidence} is below ${minConfidence}.`,
3383
- [eventEvidence(event, "confidence")],
4056
+ [eventEvidence2(event, "confidence")],
3384
4057
  { minConfidence },
3385
4058
  event.confidence
3386
4059
  )
@@ -3392,7 +4065,7 @@ function createStructureRelationshipRule(options = {}) {
3392
4065
  failFinding(
3393
4066
  "structure.relationship",
3394
4067
  "Event parentId points to itself.",
3395
- [eventEvidence(event, "parentId")],
4068
+ [eventEvidence2(event, "parentId")],
3396
4069
  "parentId references a distinct event",
3397
4070
  "self"
3398
4071
  )
@@ -3409,7 +4082,7 @@ function createStructureRelationshipRule(options = {}) {
3409
4082
  failFinding(
3410
4083
  "structure.relationship",
3411
4084
  "Parent event starts after child event.",
3412
- [eventEvidence(parent), eventEvidence(event, "parentId")],
4085
+ [eventEvidence2(parent), eventEvidence2(event, "parentId")],
3413
4086
  "parent start <= child start",
3414
4087
  { parentEventId: parent.eventId, childEventId: event.eventId }
3415
4088
  )
@@ -3423,7 +4096,7 @@ function createStructureRelationshipRule(options = {}) {
3423
4096
  failFinding(
3424
4097
  "structure.relationship",
3425
4098
  "Trace parentSpanId does not match parent spanId.",
3426
- [eventEvidence(event, "trace.parentSpanId")],
4099
+ [eventEvidence2(event, "trace.parentSpanId")],
3427
4100
  { parentSpanId: parent.trace.spanId },
3428
4101
  actual ?? "missing"
3429
4102
  )
@@ -3452,7 +4125,7 @@ function createStructureParallelWidthRule(options) {
3452
4125
  "structure.parallelWidth",
3453
4126
  `Parent ${parentId} has ${children.length} children, exceeding ${options.maxChildren}.`,
3454
4127
  [
3455
- ...parent ? [eventEvidence(parent)] : [{ runId: context.selectedRun?.runId, eventId: parentId }],
4128
+ ...parent ? [eventEvidence2(parent)] : [{ runId: context.selectedRun?.runId, eventId: parentId }],
3456
4129
  ...children.map((child) => ({
3457
4130
  runId: child.event.runId,
3458
4131
  eventId: child.event.eventId,
@@ -3500,7 +4173,7 @@ function createStructureParallelWidthRule(options) {
3500
4173
  failFinding(
3501
4174
  "structure.parallelWidth",
3502
4175
  `Concurrent event width ${maxActive.length} exceeded ${options.maxConcurrent}.`,
3503
- maxActive.map((event) => eventEvidence(event)),
4176
+ maxActive.map((event) => eventEvidence2(event)),
3504
4177
  { maxConcurrent: options.maxConcurrent },
3505
4178
  maxActive.length
3506
4179
  )
@@ -3543,7 +4216,7 @@ function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
3543
4216
  failFinding(
3544
4217
  ruleId,
3545
4218
  `Forbidden ${label} ${name} appeared.`,
3546
- [eventEvidence(event)],
4219
+ [eventEvidence2(event)],
3547
4220
  `${label} absent`,
3548
4221
  name
3549
4222
  )
@@ -3554,7 +4227,7 @@ function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
3554
4227
  failFinding(
3555
4228
  ruleId,
3556
4229
  `${label[0].toUpperCase()}${label.slice(1)} ${name} is not in the allowed set.`,
3557
- [eventEvidence(event)],
4230
+ [eventEvidence2(event)],
3558
4231
  [...allowed].sort(),
3559
4232
  name
3560
4233
  )
@@ -3577,7 +4250,7 @@ function createSignalRule(ruleId, label, options, selectEvents, nameForEvent) {
3577
4250
  failFinding(
3578
4251
  ruleId,
3579
4252
  `${label[0].toUpperCase()}${label.slice(1)} count ${events.length} exceeded maximum ${options.maxCount}.`,
3580
- events.map((event) => eventEvidence(event)),
4253
+ events.map((event) => eventEvidence2(event)),
3581
4254
  { maxCount: options.maxCount },
3582
4255
  events.length
3583
4256
  )
@@ -3631,7 +4304,7 @@ function createSafetyRedactionRule(options = {}) {
3631
4304
  failFinding(
3632
4305
  "safety.redaction",
3633
4306
  `Sensitive-looking field at ${entry.path} is not redacted.`,
3634
- [eventEvidence(event, entry.path)],
4307
+ [eventEvidence2(event, entry.path)],
3635
4308
  "redaction marker",
3636
4309
  { path: entry.path, valueType: valueType(entry.value) },
3637
4310
  {
@@ -3665,7 +4338,7 @@ function createSafetyRawContentRule(options = {}) {
3665
4338
  failFinding(
3666
4339
  "safety.rawPrompt",
3667
4340
  `Raw content-like field ${entry.path} is present.`,
3668
- [eventEvidence(event, entry.path)],
4341
+ [eventEvidence2(event, entry.path)],
3669
4342
  "metadata-only trace fields",
3670
4343
  { path: entry.path, valueType: valueType(entry.value) },
3671
4344
  {
@@ -3703,7 +4376,7 @@ function createSafetySecretPatternRule(options = {}) {
3703
4376
  failFinding(
3704
4377
  "safety.secretPattern",
3705
4378
  `Secret-like pattern ${pattern.id} matched at ${entry.path}.`,
3706
- [eventEvidence(event, entry.path)],
4379
+ [eventEvidence2(event, entry.path)],
3707
4380
  "no secret-like strings",
3708
4381
  { pattern: pattern.id, path: entry.path },
3709
4382
  {
@@ -3736,7 +4409,7 @@ function createSafetyOversizedAttributeRule(options) {
3736
4409
  failFinding(
3737
4410
  "safety.oversizedAttribute",
3738
4411
  `String at ${entry.path} exceeds ${options.maxStringLength} characters.`,
3739
- [eventEvidence(event, entry.path)],
4412
+ [eventEvidence2(event, entry.path)],
3740
4413
  { maxStringLength: options.maxStringLength },
3741
4414
  { path: entry.path, length: entry.value.length },
3742
4415
  {
@@ -3753,7 +4426,7 @@ function createSafetyOversizedAttributeRule(options) {
3753
4426
  failFinding(
3754
4427
  "safety.oversizedAttribute",
3755
4428
  `Array at ${entry.path} exceeds ${options.maxArrayLength} items.`,
3756
- [eventEvidence(event, entry.path)],
4429
+ [eventEvidence2(event, entry.path)],
3757
4430
  { maxArrayLength: options.maxArrayLength },
3758
4431
  { path: entry.path, length: entry.value.length },
3759
4432
  {
@@ -3770,7 +4443,7 @@ function createSafetyOversizedAttributeRule(options) {
3770
4443
  failFinding(
3771
4444
  "safety.oversizedAttribute",
3772
4445
  `Object at ${entry.path} exceeds ${options.maxObjectKeys} keys.`,
3773
- [eventEvidence(event, entry.path)],
4446
+ [eventEvidence2(event, entry.path)],
3774
4447
  { maxObjectKeys: options.maxObjectKeys },
3775
4448
  { path: entry.path, keys: Object.keys(entry.value).length },
3776
4449
  {
@@ -3789,7 +4462,7 @@ function createSafetyOversizedAttributeRule(options) {
3789
4462
  failFinding(
3790
4463
  "safety.oversizedAttribute",
3791
4464
  `Value at ${entry.path} exceeds ${options.maxSerializedBytes} serialized bytes.`,
3792
- [eventEvidence(event, entry.path)],
4465
+ [eventEvidence2(event, entry.path)],
3793
4466
  { maxSerializedBytes: options.maxSerializedBytes },
3794
4467
  { path: entry.path, bytes },
3795
4468
  {
@@ -3916,7 +4589,7 @@ function createBaselineRegressionRule(options) {
3916
4589
  path: "guardrail",
3917
4590
  expected: guardrailShape(baselineContext),
3918
4591
  actual: guardrailShape(context),
3919
- evidence: guardrailEvents(context)[0] ? [eventEvidence(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
4592
+ evidence: guardrailEvents(context)[0] ? [eventEvidence2(guardrailEvents(context)[0], "guardrail")] : runEvidence(context.selectedRun)
3920
4593
  }
3921
4594
  ];
3922
4595
  for (const comparison of comparisons) {
@@ -4086,13 +4759,16 @@ exports.createToolUsageRule = createToolUsageRule;
4086
4759
  exports.defineTraceContract = defineTraceContract;
4087
4760
  exports.deriveFailureFacts = deriveFailureFacts;
4088
4761
  exports.deriveRelationshipFacts = deriveRelationshipFacts;
4762
+ exports.evaluateToolArgumentValue = evaluateToolArgumentValue;
4089
4763
  exports.evaluateTraceContract = evaluateTraceContract;
4090
4764
  exports.evaluateTraceContractRead = evaluateTraceContractRead;
4091
4765
  exports.explainTraceContract = explainTraceContract;
4766
+ exports.extractToolArgumentPayload = extractToolArgumentPayload;
4092
4767
  exports.formatProgrammaticDiagnostic = formatProgrammaticDiagnostic;
4093
4768
  exports.lintTraceContract = lintTraceContract;
4094
4769
  exports.projectLogicalEvents = projectLogicalEvents;
4095
4770
  exports.resolveCanonicalToolName = resolveCanonicalToolName;
4771
+ exports.resolveJsonPointer = resolveJsonPointer;
4096
4772
  exports.resolveTraceContractScope = resolveTraceContractScope;
4097
4773
  exports.runTraceChecks = runTraceChecks;
4098
4774
  exports.summarizeSemanticParity = summarizeSemanticParity;